<!DOCTYPE html>
<html lang="en-US" dir="ltr">
  <head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width,initial-scale=1">
    <title>操作系统基础知识 | VitePress</title>
    <meta name="description" content="A VitePress site">
    <link rel="preload stylesheet" href="/notebook/assets/style.3dbfd0c2.css" as="style">
    
    <script type="module" src="/notebook/assets/app.8aaa4cbe.js"></script>
    <link rel="preload" href="/notebook/assets/inter-roman-latin.2ed14f66.woff2" as="font" type="font/woff2" crossorigin="">
    <link rel="modulepreload" href="/notebook/assets/chunks/framework.1336c4e5.js">
    <link rel="modulepreload" href="/notebook/assets/chunks/theme.20cddc0c.js">
    <link rel="modulepreload" href="/notebook/assets/计算机基础_计算机基础_操作系统.md.0f75d113.lean.js">
    <script id="check-dark-light">(()=>{const e=localStorage.getItem("vitepress-theme-appearance")||"",a=window.matchMedia("(prefers-color-scheme: dark)").matches;(!e||e==="auto"?a:e==="dark")&&document.documentElement.classList.add("dark")})();</script>
  </head>
  <body>
    <div id="app"><div class="Layout" data-v-255ec12d><!--[--><!--]--><!--[--><span tabindex="-1" data-v-ae3e3f51></span><a href="#VPContent" class="VPSkipLink visually-hidden" data-v-ae3e3f51> Skip to content </a><!--]--><!----><header class="VPNav" data-v-255ec12d data-v-7e5bc4a5><div class="VPNavBar has-sidebar" data-v-7e5bc4a5 data-v-0937f67c><div class="container" data-v-0937f67c><div class="title" data-v-0937f67c><div class="VPNavBarTitle has-sidebar" data-v-0937f67c data-v-86d1bed8><a class="title" href="/notebook/" data-v-86d1bed8><!--[--><!--]--><!--[--><img class="VPImage logo" src="/notebook/Vue.png" alt data-v-8426fc1a><!--]--><!--[-->任硕的文档<!--]--><!--[--><!--]--></a></div></div><div class="content" data-v-0937f67c><div class="curtain" data-v-0937f67c></div><div class="content-body" data-v-0937f67c><!--[--><!--]--><div class="VPNavBarSearch search" style="--vp-meta-key:&#39;Meta&#39;;" data-v-0937f67c><!--[--><!----><div id="docsearch"><button type="button" class="DocSearch DocSearch-Button" aria-label="Search"><span class="DocSearch-Button-Container"><svg class="DocSearch-Search-Icon" width="20" height="20" viewBox="0 0 20 20" aria-label="search icon"><path d="M14.386 14.386l4.0877 4.0877-4.0877-4.0877c-2.9418 2.9419-7.7115 2.9419-10.6533 0-2.9419-2.9418-2.9419-7.7115 0-10.6533 2.9418-2.9419 7.7115-2.9419 10.6533 0 2.9419 2.9418 2.9419 7.7115 0 10.6533z" stroke="currentColor" fill="none" fill-rule="evenodd" stroke-linecap="round" stroke-linejoin="round"></path></svg><span class="DocSearch-Button-Placeholder">Search</span></span><span class="DocSearch-Button-Keys"><kbd class="DocSearch-Button-Key"></kbd><kbd class="DocSearch-Button-Key">K</kbd></span></button></div><!--]--></div><nav aria-labelledby="main-nav-aria-label" class="VPNavBarMenu menu" data-v-0937f67c data-v-7f418b0f><span id="main-nav-aria-label" class="visually-hidden" data-v-7f418b0f>Main Navigation</span><!--[--><!--[--><div class="VPFlyout VPNavBarMenuGroup" data-v-7f418b0f data-v-a7b5672a><button type="button" class="button" aria-haspopup="true" aria-expanded="false" data-v-a7b5672a><span class="text" data-v-a7b5672a><!----><span data-v-a7b5672a>Java学前端</span><svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false" viewbox="0 0 24 24" class="text-icon" data-v-a7b5672a><path d="M12,16c-0.3,0-0.5-0.1-0.7-0.3l-6-6c-0.4-0.4-0.4-1,0-1.4s1-0.4,1.4,0l5.3,5.3l5.3-5.3c0.4-0.4,1-0.4,1.4,0s0.4,1,0,1.4l-6,6C12.5,15.9,12.3,16,12,16z"></path></svg></span></button><div class="menu" data-v-a7b5672a><div class="VPMenu" data-v-a7b5672a data-v-e7ea1737><div class="items" data-v-e7ea1737><!--[--><!--[--><div class="VPMenuGroup" data-v-e7ea1737 data-v-69e747b5><!----><!--[--><!--[--><div class="VPMenuLink" data-v-69e747b5 data-v-2f2cfafc><a class="VPLink link" href="/notebook/Java%E5%AD%A6%E5%89%8D%E7%AB%AF/HTML+JS.html" data-v-2f2cfafc><!--[-->HTML+JS<!--]--></a></div><!--]--><!--[--><div class="VPMenuLink" data-v-69e747b5 data-v-2f2cfafc><a class="VPLink link" href="/notebook/Java%E5%AD%A6%E5%89%8D%E7%AB%AF/CSS.html" data-v-2f2cfafc><!--[-->CSS<!--]--></a></div><!--]--><!--[--><div class="VPMenuLink" data-v-69e747b5 data-v-2f2cfafc><a class="VPLink link" href="/notebook/Java%E5%AD%A6%E5%89%8D%E7%AB%AF/Vue2+%E7%BB%84%E4%BB%B6.html" data-v-2f2cfafc><!--[-->Vue2+组件<!--]--></a></div><!--]--><!--[--><div class="VPMenuLink" data-v-69e747b5 data-v-2f2cfafc><a class="VPLink link" href="/notebook/Java%E5%AD%A6%E5%89%8D%E7%AB%AF/Vue3+%E7%BB%84%E4%BB%B6.html" data-v-2f2cfafc><!--[-->Vue3+组件<!--]--></a></div><!--]--><!--[--><div class="VPMenuLink" data-v-69e747b5 data-v-2f2cfafc><a class="VPLink link" href="/notebook/Java%E5%AD%A6%E5%89%8D%E7%AB%AF/React.html" data-v-2f2cfafc><!--[-->React<!--]--></a></div><!--]--><!--]--></div><!--]--><!--]--></div><!--[--><!--]--></div></div></div><!--]--><!--[--><div class="VPFlyout VPNavBarMenuGroup" data-v-7f418b0f data-v-a7b5672a><button type="button" class="button" aria-haspopup="true" aria-expanded="false" data-v-a7b5672a><span class="text" data-v-a7b5672a><!----><span data-v-a7b5672a>软件测试</span><svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false" viewbox="0 0 24 24" class="text-icon" data-v-a7b5672a><path d="M12,16c-0.3,0-0.5-0.1-0.7-0.3l-6-6c-0.4-0.4-0.4-1,0-1.4s1-0.4,1.4,0l5.3,5.3l5.3-5.3c0.4-0.4,1-0.4,1.4,0s0.4,1,0,1.4l-6,6C12.5,15.9,12.3,16,12,16z"></path></svg></span></button><div class="menu" data-v-a7b5672a><div class="VPMenu" data-v-a7b5672a data-v-e7ea1737><div class="items" data-v-e7ea1737><!--[--><!--[--><div class="VPMenuGroup" data-v-e7ea1737 data-v-69e747b5><!----><!--[--><!--[--><div class="VPMenuLink" data-v-69e747b5 data-v-2f2cfafc><a class="VPLink link" href="/notebook/%E8%BD%AF%E4%BB%B6%E6%B5%8B%E8%AF%95/%E6%B5%8B%E8%AF%95%E5%9F%BA%E7%A1%80.html" data-v-2f2cfafc><!--[-->测试基础<!--]--></a></div><!--]--><!--[--><div class="VPMenuLink" data-v-69e747b5 data-v-2f2cfafc><a class="VPLink link" href="/notebook/%E8%BD%AF%E4%BB%B6%E6%B5%8B%E8%AF%95/%E5%8E%8B%E5%8A%9B%E6%B5%8B%E8%AF%95.html" data-v-2f2cfafc><!--[-->压力测试<!--]--></a></div><!--]--><!--]--></div><!--]--><!--]--></div><!--[--><!--]--></div></div></div><!--]--><!--[--><div class="VPFlyout VPNavBarMenuGroup" data-v-7f418b0f data-v-a7b5672a><button type="button" class="button" aria-haspopup="true" aria-expanded="false" data-v-a7b5672a><span class="text" data-v-a7b5672a><!----><span data-v-a7b5672a>多线程</span><svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false" viewbox="0 0 24 24" class="text-icon" data-v-a7b5672a><path d="M12,16c-0.3,0-0.5-0.1-0.7-0.3l-6-6c-0.4-0.4-0.4-1,0-1.4s1-0.4,1.4,0l5.3,5.3l5.3-5.3c0.4-0.4,1-0.4,1.4,0s0.4,1,0,1.4l-6,6C12.5,15.9,12.3,16,12,16z"></path></svg></span></button><div class="menu" data-v-a7b5672a><div class="VPMenu" data-v-a7b5672a data-v-e7ea1737><div class="items" data-v-e7ea1737><!--[--><!--[--><div class="VPMenuGroup" data-v-e7ea1737 data-v-69e747b5><!----><!--[--><!--[--><div class="VPMenuLink" data-v-69e747b5 data-v-2f2cfafc><a class="VPLink link" href="/notebook/%E5%B9%B6%E5%8F%91%20&amp;%20%E5%A4%9A%E7%BA%BF%E7%A8%8B/%E5%9F%BA%E7%A1%80%E7%AF%87.html" data-v-2f2cfafc><!--[-->基础篇<!--]--></a></div><!--]--><!--[--><div class="VPMenuLink" data-v-69e747b5 data-v-2f2cfafc><a class="VPLink link" href="/notebook/%E5%B9%B6%E5%8F%91%20&amp;%20%E5%A4%9A%E7%BA%BF%E7%A8%8B/%E5%B9%B6%E5%8F%91%E5%AE%8C%E5%96%84.html" data-v-2f2cfafc><!--[-->进阶篇<!--]--></a></div><!--]--><!--]--></div><!--]--><!--]--></div><!--[--><!--]--></div></div></div><!--]--><!--[--><div class="VPFlyout VPNavBarMenuGroup" data-v-7f418b0f data-v-a7b5672a><button type="button" class="button" aria-haspopup="true" aria-expanded="false" data-v-a7b5672a><span class="text" data-v-a7b5672a><!----><span data-v-a7b5672a>开发工具</span><svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false" viewbox="0 0 24 24" class="text-icon" data-v-a7b5672a><path d="M12,16c-0.3,0-0.5-0.1-0.7-0.3l-6-6c-0.4-0.4-0.4-1,0-1.4s1-0.4,1.4,0l5.3,5.3l5.3-5.3c0.4-0.4,1-0.4,1.4,0s0.4,1,0,1.4l-6,6C12.5,15.9,12.3,16,12,16z"></path></svg></span></button><div class="menu" data-v-a7b5672a><div class="VPMenu" data-v-a7b5672a data-v-e7ea1737><div class="items" data-v-e7ea1737><!--[--><!--[--><div class="VPMenuGroup" data-v-e7ea1737 data-v-69e747b5><!----><!--[--><!--[--><div class="VPMenuLink" data-v-69e747b5 data-v-2f2cfafc><a class="VPLink link" href="/notebook/IDEA/Chrome.html" data-v-2f2cfafc><!--[-->Chrome<!--]--></a></div><!--]--><!--[--><div class="VPMenuLink" data-v-69e747b5 data-v-2f2cfafc><a class="VPLink link" href="/notebook/IDEA/IDEA%E5%9F%BA%E7%A1%80.html" data-v-2f2cfafc><!--[-->IDEA基础<!--]--></a></div><!--]--><!--[--><div class="VPMenuLink" data-v-69e747b5 data-v-2f2cfafc><a class="VPLink link" href="/notebook/IDEA/IDEA%E6%8F%92%E4%BB%B6.html" data-v-2f2cfafc><!--[-->IDEA插件<!--]--></a></div><!--]--><!--[--><div class="VPMenuLink" data-v-69e747b5 data-v-2f2cfafc><a class="VPLink link" href="/notebook/IDEA/VS%20Code.html" data-v-2f2cfafc><!--[-->VS Code<!--]--></a></div><!--]--><!--]--></div><!--]--><!--]--></div><!--[--><!--]--></div></div></div><!--]--><!--[--><div class="VPFlyout VPNavBarMenuGroup" data-v-7f418b0f data-v-a7b5672a><button type="button" class="button" aria-haspopup="true" aria-expanded="false" data-v-a7b5672a><span class="text" data-v-a7b5672a><!----><span data-v-a7b5672a>消息中间件</span><svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false" viewbox="0 0 24 24" class="text-icon" data-v-a7b5672a><path d="M12,16c-0.3,0-0.5-0.1-0.7-0.3l-6-6c-0.4-0.4-0.4-1,0-1.4s1-0.4,1.4,0l5.3,5.3l5.3-5.3c0.4-0.4,1-0.4,1.4,0s0.4,1,0,1.4l-6,6C12.5,15.9,12.3,16,12,16z"></path></svg></span></button><div class="menu" data-v-a7b5672a><div class="VPMenu" data-v-a7b5672a data-v-e7ea1737><div class="items" data-v-e7ea1737><!--[--><!--[--><div class="VPMenuGroup" data-v-e7ea1737 data-v-69e747b5><!----><!--[--><!--[--><div class="VPMenuLink" data-v-69e747b5 data-v-2f2cfafc><a class="VPLink link" href="/notebook/%E6%B6%88%E6%81%AF%E4%B8%AD%E9%97%B4%E4%BB%B6/RabbitMQ.html" data-v-2f2cfafc><!--[-->RabbitMQ<!--]--></a></div><!--]--><!--[--><div class="VPMenuLink" data-v-69e747b5 data-v-2f2cfafc><a class="VPLink link" href="/notebook/%E6%B6%88%E6%81%AF%E4%B8%AD%E9%97%B4%E4%BB%B6/RocketMQ.html" data-v-2f2cfafc><!--[-->RocketMQ<!--]--></a></div><!--]--><!--[--><div class="VPMenuLink" data-v-69e747b5 data-v-2f2cfafc><a class="VPLink link" href="/notebook/%E6%B6%88%E6%81%AF%E4%B8%AD%E9%97%B4%E4%BB%B6/Kafka.html" data-v-2f2cfafc><!--[-->Kafka<!--]--></a></div><!--]--><!--[--><div class="VPMenuLink" data-v-69e747b5 data-v-2f2cfafc><a class="VPLink link" href="/notebook/%E6%B6%88%E6%81%AF%E4%B8%AD%E9%97%B4%E4%BB%B6/Canal.html" data-v-2f2cfafc><!--[-->Canal<!--]--></a></div><!--]--><!--]--></div><!--]--><!--]--></div><!--[--><!--]--></div></div></div><!--]--><!--]--></nav><!----><div class="VPNavBarAppearance appearance" data-v-0937f67c data-v-f6a63727><button class="VPSwitch VPSwitchAppearance" type="button" role="switch" title="toggle dark mode" aria-checked="false" data-v-f6a63727 data-v-82b282f1 data-v-f3c41672><span class="check" data-v-f3c41672><span class="icon" data-v-f3c41672><!--[--><svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false" viewbox="0 0 24 24" class="sun" data-v-82b282f1><path d="M12,18c-3.3,0-6-2.7-6-6s2.7-6,6-6s6,2.7,6,6S15.3,18,12,18zM12,8c-2.2,0-4,1.8-4,4c0,2.2,1.8,4,4,4c2.2,0,4-1.8,4-4C16,9.8,14.2,8,12,8z"></path><path d="M12,4c-0.6,0-1-0.4-1-1V1c0-0.6,0.4-1,1-1s1,0.4,1,1v2C13,3.6,12.6,4,12,4z"></path><path d="M12,24c-0.6,0-1-0.4-1-1v-2c0-0.6,0.4-1,1-1s1,0.4,1,1v2C13,23.6,12.6,24,12,24z"></path><path d="M5.6,6.6c-0.3,0-0.5-0.1-0.7-0.3L3.5,4.9c-0.4-0.4-0.4-1,0-1.4s1-0.4,1.4,0l1.4,1.4c0.4,0.4,0.4,1,0,1.4C6.2,6.5,5.9,6.6,5.6,6.6z"></path><path d="M19.8,20.8c-0.3,0-0.5-0.1-0.7-0.3l-1.4-1.4c-0.4-0.4-0.4-1,0-1.4s1-0.4,1.4,0l1.4,1.4c0.4,0.4,0.4,1,0,1.4C20.3,20.7,20,20.8,19.8,20.8z"></path><path d="M3,13H1c-0.6,0-1-0.4-1-1s0.4-1,1-1h2c0.6,0,1,0.4,1,1S3.6,13,3,13z"></path><path d="M23,13h-2c-0.6,0-1-0.4-1-1s0.4-1,1-1h2c0.6,0,1,0.4,1,1S23.6,13,23,13z"></path><path d="M4.2,20.8c-0.3,0-0.5-0.1-0.7-0.3c-0.4-0.4-0.4-1,0-1.4l1.4-1.4c0.4-0.4,1-0.4,1.4,0s0.4,1,0,1.4l-1.4,1.4C4.7,20.7,4.5,20.8,4.2,20.8z"></path><path d="M18.4,6.6c-0.3,0-0.5-0.1-0.7-0.3c-0.4-0.4-0.4-1,0-1.4l1.4-1.4c0.4-0.4,1-0.4,1.4,0s0.4,1,0,1.4l-1.4,1.4C18.9,6.5,18.6,6.6,18.4,6.6z"></path></svg><svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false" viewbox="0 0 24 24" class="moon" data-v-82b282f1><path d="M12.1,22c-0.3,0-0.6,0-0.9,0c-5.5-0.5-9.5-5.4-9-10.9c0.4-4.8,4.2-8.6,9-9c0.4,0,0.8,0.2,1,0.5c0.2,0.3,0.2,0.8-0.1,1.1c-2,2.7-1.4,6.4,1.3,8.4c2.1,1.6,5,1.6,7.1,0c0.3-0.2,0.7-0.3,1.1-0.1c0.3,0.2,0.5,0.6,0.5,1c-0.2,2.7-1.5,5.1-3.6,6.8C16.6,21.2,14.4,22,12.1,22zM9.3,4.4c-2.9,1-5,3.6-5.2,6.8c-0.4,4.4,2.8,8.3,7.2,8.7c2.1,0.2,4.2-0.4,5.8-1.8c1.1-0.9,1.9-2.1,2.4-3.4c-2.5,0.9-5.3,0.5-7.5-1.1C9.2,11.4,8.1,7.7,9.3,4.4z"></path></svg><!--]--></span></span></button></div><div class="VPSocialLinks VPNavBarSocialLinks social-links" data-v-0937f67c data-v-0394ad82 data-v-7bc22406><!--[--><a class="VPSocialLink no-icon" href="https://github.com/renshuo123/renshuo123.github.io" aria-label="github" target="_blank" rel="noopener" data-v-7bc22406 data-v-f80f8133><svg role="img" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><title>GitHub</title><path d="M12 .297c-6.63 0-12 5.373-12 12 0 5.303 3.438 9.8 8.205 11.385.6.113.82-.258.82-.577 0-.285-.01-1.04-.015-2.04-3.338.724-4.042-1.61-4.042-1.61C4.422 18.07 3.633 17.7 3.633 17.7c-1.087-.744.084-.729.084-.729 1.205.084 1.838 1.236 1.838 1.236 1.07 1.835 2.809 1.305 3.495.998.108-.776.417-1.305.76-1.605-2.665-.3-5.466-1.332-5.466-5.93 0-1.31.465-2.38 1.235-3.22-.135-.303-.54-1.523.105-3.176 0 0 1.005-.322 3.3 1.23.96-.267 1.98-.399 3-.405 1.02.006 2.04.138 3 .405 2.28-1.552 3.285-1.23 3.285-1.23.645 1.653.24 2.873.12 3.176.765.84 1.23 1.91 1.23 3.22 0 4.61-2.805 5.625-5.475 5.92.42.36.81 1.096.81 2.22 0 1.606-.015 2.896-.015 3.286 0 .315.21.69.825.57C20.565 22.092 24 17.592 24 12.297c0-6.627-5.373-12-12-12"/></svg></a><a class="VPSocialLink no-icon" href="#" aria-label="twitter" target="_blank" rel="noopener" data-v-7bc22406 data-v-f80f8133><svg role="img" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><title>Twitter</title><path d="M23.953 4.57a10 10 0 01-2.825.775 4.958 4.958 0 002.163-2.723c-.951.555-2.005.959-3.127 1.184a4.92 4.92 0 00-8.384 4.482C7.69 8.095 4.067 6.13 1.64 3.162a4.822 4.822 0 00-.666 2.475c0 1.71.87 3.213 2.188 4.096a4.904 4.904 0 01-2.228-.616v.06a4.923 4.923 0 003.946 4.827 4.996 4.996 0 01-2.212.085 4.936 4.936 0 004.604 3.417 9.867 9.867 0 01-6.102 2.105c-.39 0-.779-.023-1.17-.067a13.995 13.995 0 007.557 2.209c9.053 0 13.998-7.496 13.998-13.985 0-.21 0-.42-.015-.63A9.935 9.935 0 0024 4.59z"/></svg></a><a class="VPSocialLink no-icon" href="https://github.com/" aria-label target="_blank" rel="noopener" data-v-7bc22406 data-v-f80f8133><svg t="1676028692954" class="icon" ...</path></svg></a><!--]--></div><div class="VPFlyout VPNavBarExtra extra" data-v-0937f67c data-v-40855f84 data-v-a7b5672a><button type="button" class="button" aria-haspopup="true" aria-expanded="false" aria-label="extra navigation" data-v-a7b5672a><svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false" viewbox="0 0 24 24" class="icon" data-v-a7b5672a><circle cx="12" cy="12" r="2"></circle><circle cx="19" cy="12" r="2"></circle><circle cx="5" cy="12" r="2"></circle></svg></button><div class="menu" data-v-a7b5672a><div class="VPMenu" data-v-a7b5672a data-v-e7ea1737><!----><!--[--><!--[--><!----><div class="group" data-v-40855f84><div class="item appearance" data-v-40855f84><p class="label" data-v-40855f84>Appearance</p><div class="appearance-action" data-v-40855f84><button class="VPSwitch VPSwitchAppearance" type="button" role="switch" title="toggle dark mode" aria-checked="false" data-v-40855f84 data-v-82b282f1 data-v-f3c41672><span class="check" data-v-f3c41672><span class="icon" data-v-f3c41672><!--[--><svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false" viewbox="0 0 24 24" class="sun" data-v-82b282f1><path d="M12,18c-3.3,0-6-2.7-6-6s2.7-6,6-6s6,2.7,6,6S15.3,18,12,18zM12,8c-2.2,0-4,1.8-4,4c0,2.2,1.8,4,4,4c2.2,0,4-1.8,4-4C16,9.8,14.2,8,12,8z"></path><path d="M12,4c-0.6,0-1-0.4-1-1V1c0-0.6,0.4-1,1-1s1,0.4,1,1v2C13,3.6,12.6,4,12,4z"></path><path d="M12,24c-0.6,0-1-0.4-1-1v-2c0-0.6,0.4-1,1-1s1,0.4,1,1v2C13,23.6,12.6,24,12,24z"></path><path d="M5.6,6.6c-0.3,0-0.5-0.1-0.7-0.3L3.5,4.9c-0.4-0.4-0.4-1,0-1.4s1-0.4,1.4,0l1.4,1.4c0.4,0.4,0.4,1,0,1.4C6.2,6.5,5.9,6.6,5.6,6.6z"></path><path d="M19.8,20.8c-0.3,0-0.5-0.1-0.7-0.3l-1.4-1.4c-0.4-0.4-0.4-1,0-1.4s1-0.4,1.4,0l1.4,1.4c0.4,0.4,0.4,1,0,1.4C20.3,20.7,20,20.8,19.8,20.8z"></path><path d="M3,13H1c-0.6,0-1-0.4-1-1s0.4-1,1-1h2c0.6,0,1,0.4,1,1S3.6,13,3,13z"></path><path d="M23,13h-2c-0.6,0-1-0.4-1-1s0.4-1,1-1h2c0.6,0,1,0.4,1,1S23.6,13,23,13z"></path><path d="M4.2,20.8c-0.3,0-0.5-0.1-0.7-0.3c-0.4-0.4-0.4-1,0-1.4l1.4-1.4c0.4-0.4,1-0.4,1.4,0s0.4,1,0,1.4l-1.4,1.4C4.7,20.7,4.5,20.8,4.2,20.8z"></path><path d="M18.4,6.6c-0.3,0-0.5-0.1-0.7-0.3c-0.4-0.4-0.4-1,0-1.4l1.4-1.4c0.4-0.4,1-0.4,1.4,0s0.4,1,0,1.4l-1.4,1.4C18.9,6.5,18.6,6.6,18.4,6.6z"></path></svg><svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false" viewbox="0 0 24 24" class="moon" data-v-82b282f1><path d="M12.1,22c-0.3,0-0.6,0-0.9,0c-5.5-0.5-9.5-5.4-9-10.9c0.4-4.8,4.2-8.6,9-9c0.4,0,0.8,0.2,1,0.5c0.2,0.3,0.2,0.8-0.1,1.1c-2,2.7-1.4,6.4,1.3,8.4c2.1,1.6,5,1.6,7.1,0c0.3-0.2,0.7-0.3,1.1-0.1c0.3,0.2,0.5,0.6,0.5,1c-0.2,2.7-1.5,5.1-3.6,6.8C16.6,21.2,14.4,22,12.1,22zM9.3,4.4c-2.9,1-5,3.6-5.2,6.8c-0.4,4.4,2.8,8.3,7.2,8.7c2.1,0.2,4.2-0.4,5.8-1.8c1.1-0.9,1.9-2.1,2.4-3.4c-2.5,0.9-5.3,0.5-7.5-1.1C9.2,11.4,8.1,7.7,9.3,4.4z"></path></svg><!--]--></span></span></button></div></div></div><div class="group" data-v-40855f84><div class="item social-links" data-v-40855f84><div class="VPSocialLinks social-links-list" data-v-40855f84 data-v-7bc22406><!--[--><a class="VPSocialLink no-icon" href="https://github.com/renshuo123/renshuo123.github.io" aria-label="github" target="_blank" rel="noopener" data-v-7bc22406 data-v-f80f8133><svg role="img" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><title>GitHub</title><path d="M12 .297c-6.63 0-12 5.373-12 12 0 5.303 3.438 9.8 8.205 11.385.6.113.82-.258.82-.577 0-.285-.01-1.04-.015-2.04-3.338.724-4.042-1.61-4.042-1.61C4.422 18.07 3.633 17.7 3.633 17.7c-1.087-.744.084-.729.084-.729 1.205.084 1.838 1.236 1.838 1.236 1.07 1.835 2.809 1.305 3.495.998.108-.776.417-1.305.76-1.605-2.665-.3-5.466-1.332-5.466-5.93 0-1.31.465-2.38 1.235-3.22-.135-.303-.54-1.523.105-3.176 0 0 1.005-.322 3.3 1.23.96-.267 1.98-.399 3-.405 1.02.006 2.04.138 3 .405 2.28-1.552 3.285-1.23 3.285-1.23.645 1.653.24 2.873.12 3.176.765.84 1.23 1.91 1.23 3.22 0 4.61-2.805 5.625-5.475 5.92.42.36.81 1.096.81 2.22 0 1.606-.015 2.896-.015 3.286 0 .315.21.69.825.57C20.565 22.092 24 17.592 24 12.297c0-6.627-5.373-12-12-12"/></svg></a><a class="VPSocialLink no-icon" href="#" aria-label="twitter" target="_blank" rel="noopener" data-v-7bc22406 data-v-f80f8133><svg role="img" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><title>Twitter</title><path d="M23.953 4.57a10 10 0 01-2.825.775 4.958 4.958 0 002.163-2.723c-.951.555-2.005.959-3.127 1.184a4.92 4.92 0 00-8.384 4.482C7.69 8.095 4.067 6.13 1.64 3.162a4.822 4.822 0 00-.666 2.475c0 1.71.87 3.213 2.188 4.096a4.904 4.904 0 01-2.228-.616v.06a4.923 4.923 0 003.946 4.827 4.996 4.996 0 01-2.212.085 4.936 4.936 0 004.604 3.417 9.867 9.867 0 01-6.102 2.105c-.39 0-.779-.023-1.17-.067a13.995 13.995 0 007.557 2.209c9.053 0 13.998-7.496 13.998-13.985 0-.21 0-.42-.015-.63A9.935 9.935 0 0024 4.59z"/></svg></a><a class="VPSocialLink no-icon" href="https://github.com/" aria-label target="_blank" rel="noopener" data-v-7bc22406 data-v-f80f8133><svg t="1676028692954" class="icon" ...</path></svg></a><!--]--></div></div></div><!--]--><!--]--></div></div></div><!--[--><!--]--><button type="button" class="VPNavBarHamburger hamburger" aria-label="mobile navigation" aria-expanded="false" aria-controls="VPNavScreen" data-v-0937f67c data-v-e5dd9c1c><span class="container" data-v-e5dd9c1c><span class="top" data-v-e5dd9c1c></span><span class="middle" data-v-e5dd9c1c></span><span class="bottom" data-v-e5dd9c1c></span></span></button></div></div></div></div><!----></header><div class="VPLocalNav reached-top" data-v-255ec12d data-v-5cfd5582><button class="menu" aria-expanded="false" aria-controls="VPSidebarNav" data-v-5cfd5582><svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false" viewbox="0 0 24 24" class="menu-icon" data-v-5cfd5582><path d="M17,11H3c-0.6,0-1-0.4-1-1s0.4-1,1-1h14c0.6,0,1,0.4,1,1S17.6,11,17,11z"></path><path d="M21,7H3C2.4,7,2,6.6,2,6s0.4-1,1-1h18c0.6,0,1,0.4,1,1S21.6,7,21,7z"></path><path d="M21,15H3c-0.6,0-1-0.4-1-1s0.4-1,1-1h18c0.6,0,1,0.4,1,1S21.6,15,21,15z"></path><path d="M17,19H3c-0.6,0-1-0.4-1-1s0.4-1,1-1h14c0.6,0,1,0.4,1,1S17.6,19,17,19z"></path></svg><span class="menu-text" data-v-5cfd5582>Menu</span></button><div class="VPLocalNavOutlineDropdown" style="--vp-vh:0px;" data-v-5cfd5582 data-v-18201f51><button data-v-18201f51>Return to top</button><!----></div></div><aside class="VPSidebar" data-v-255ec12d data-v-845b8fc6><div class="curtain" data-v-845b8fc6></div><nav class="nav" id="VPSidebarNav" aria-labelledby="sidebar-aria-label" tabindex="-1" data-v-845b8fc6><span class="visually-hidden" id="sidebar-aria-label" data-v-845b8fc6> Sidebar Navigation </span><!--[--><!--]--><!--[--><div class="group" data-v-845b8fc6><section class="VPSidebarItem level-0 collapsible collapsed" data-v-845b8fc6 data-v-9b797284><div class="item" role="button" tabindex="0" data-v-9b797284><div class="indicator" data-v-9b797284></div><h2 class="text" data-v-9b797284>Java</h2><div class="caret" role="button" aria-label="toggle section" tabindex="0" data-v-9b797284><svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false" viewbox="0 0 24 24" class="caret-icon" data-v-9b797284><path d="M9,19c-0.3,0-0.5-0.1-0.7-0.3c-0.4-0.4-0.4-1,0-1.4l5.3-5.3L8.3,6.7c-0.4-0.4-0.4-1,0-1.4s1-0.4,1.4,0l6,6c0.4,0.4,0.4,1,0,1.4l-6,6C9.5,18.9,9.3,19,9,19z"></path></svg></div></div><div class="items" data-v-9b797284><!--[--><div class="VPSidebarItem level-1 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/Java/Java%E5%9F%BA%E7%A1%80.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>Java基础</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-1 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/Java/Java%E6%96%B0%E7%89%B9%E6%80%A7.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>Java新特性</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-1 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/Java/Java%E8%BF%9B%E9%98%B6.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>Java进阶</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-1 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/Java/Java%E9%9B%86%E5%90%88.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>Java集合</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-1 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/Java/Java%E9%AB%98%E7%BA%A7.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>Java高级</p><!--]--></a><!----></div><!----></div><!--]--></div></section></div><div class="group" data-v-845b8fc6><section class="VPSidebarItem level-0 collapsible collapsed" data-v-845b8fc6 data-v-9b797284><div class="item" role="button" tabindex="0" data-v-9b797284><div class="indicator" data-v-9b797284></div><h2 class="text" data-v-9b797284>Linux</h2><div class="caret" role="button" aria-label="toggle section" tabindex="0" data-v-9b797284><svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false" viewbox="0 0 24 24" class="caret-icon" data-v-9b797284><path d="M9,19c-0.3,0-0.5-0.1-0.7-0.3c-0.4-0.4-0.4-1,0-1.4l5.3-5.3L8.3,6.7c-0.4-0.4-0.4-1,0-1.4s1-0.4,1.4,0l6,6c0.4,0.4,0.4,1,0,1.4l-6,6C9.5,18.9,9.3,19,9,19z"></path></svg></div></div><div class="items" data-v-9b797284><!--[--><div class="VPSidebarItem level-1 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/Linux/Linux%E5%9F%BA%E7%A1%80.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>Linux基础</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-1 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/Linux/Linux%E8%BF%9B%E9%98%B6.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>Linux新特性</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-1 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/Linux/Shell.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>Shell脚本</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-1 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/Linux/%E5%AE%9E%E7%94%A8%E8%84%9A%E6%9C%AC.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>实用脚本</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-1 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/Linux/%E8%BD%AF%E4%BB%B6%E9%83%A8%E7%BD%B2.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>软件部署</p><!--]--></a><!----></div><!----></div><!--]--></div></section></div><div class="group" data-v-845b8fc6><section class="VPSidebarItem level-0 collapsible collapsed" data-v-845b8fc6 data-v-9b797284><div class="item" role="button" tabindex="0" data-v-9b797284><div class="indicator" data-v-9b797284></div><h2 class="text" data-v-9b797284>Nginx</h2><div class="caret" role="button" aria-label="toggle section" tabindex="0" data-v-9b797284><svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false" viewbox="0 0 24 24" class="caret-icon" data-v-9b797284><path d="M9,19c-0.3,0-0.5-0.1-0.7-0.3c-0.4-0.4-0.4-1,0-1.4l5.3-5.3L8.3,6.7c-0.4-0.4-0.4-1,0-1.4s1-0.4,1.4,0l6,6c0.4,0.4,0.4,1,0,1.4l-6,6C9.5,18.9,9.3,19,9,19z"></path></svg></div></div><div class="items" data-v-9b797284><!--[--><div class="VPSidebarItem level-1 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/Nginx/%E5%9F%BA%E7%A1%80%E7%AF%87.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>基础篇</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-1 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/Nginx/%E8%BF%9B%E9%98%B6%E7%AF%87.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>进阶篇</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-1 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/Nginx/%E5%AE%9E%E6%88%98%E7%AF%87.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>实战篇</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-1 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/Nginx/%E9%9D%A2%E8%AF%95%E7%AF%87.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>面试篇</p><!--]--></a><!----></div><!----></div><!--]--></div></section></div><div class="group" data-v-845b8fc6><section class="VPSidebarItem level-0 collapsible collapsed" data-v-845b8fc6 data-v-9b797284><div class="item" role="button" tabindex="0" data-v-9b797284><div class="indicator" data-v-9b797284></div><h2 class="text" data-v-9b797284>SSM</h2><div class="caret" role="button" aria-label="toggle section" tabindex="0" data-v-9b797284><svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false" viewbox="0 0 24 24" class="caret-icon" data-v-9b797284><path d="M9,19c-0.3,0-0.5-0.1-0.7-0.3c-0.4-0.4-0.4-1,0-1.4l5.3-5.3L8.3,6.7c-0.4-0.4-0.4-1,0-1.4s1-0.4,1.4,0l6,6c0.4,0.4,0.4,1,0,1.4l-6,6C9.5,18.9,9.3,19,9,19z"></path></svg></div></div><div class="items" data-v-9b797284><!--[--><div class="VPSidebarItem level-1 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/SSM/Maven.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>Maven</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-1 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/SSM/Spring.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>Spring</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-1 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/SSM/SpringMVC.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>SpringMVC</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-1 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/SSM/SpringBatch.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>SpringBatch</p><!--]--></a><!----></div><!----></div><!--]--></div></section></div><div class="group" data-v-845b8fc6><section class="VPSidebarItem level-0 collapsible collapsed" data-v-845b8fc6 data-v-9b797284><div class="item" role="button" tabindex="0" data-v-9b797284><div class="indicator" data-v-9b797284></div><h2 class="text" data-v-9b797284>SpringBoot</h2><div class="caret" role="button" aria-label="toggle section" tabindex="0" data-v-9b797284><svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false" viewbox="0 0 24 24" class="caret-icon" data-v-9b797284><path d="M9,19c-0.3,0-0.5-0.1-0.7-0.3c-0.4-0.4-0.4-1,0-1.4l5.3-5.3L8.3,6.7c-0.4-0.4-0.4-1,0-1.4s1-0.4,1.4,0l6,6c0.4,0.4,0.4,1,0,1.4l-6,6C9.5,18.9,9.3,19,9,19z"></path></svg></div></div><div class="items" data-v-9b797284><!--[--><div class="VPSidebarItem level-1 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/3%E3%80%81SpringBoot/%E5%9F%BA%E7%A1%80%E7%AF%87.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>基础篇</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-1 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/3%E3%80%81SpringBoot/%E5%BA%94%E7%94%A8%E7%AF%87.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>应用篇</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-1 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/3%E3%80%81SpringBoot/%E6%96%B0%E7%89%B9%E6%80%A7.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>新特性</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-1 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/3%E3%80%81SpringBoot/%E8%BF%90%E7%BB%B4&amp;%E5%8E%9F%E7%90%86.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>运维&原理</p><!--]--></a><!----></div><!----></div><!--]--></div></section></div><div class="group" data-v-845b8fc6><section class="VPSidebarItem level-0 collapsible collapsed" data-v-845b8fc6 data-v-9b797284><div class="item" role="button" tabindex="0" data-v-9b797284><div class="indicator" data-v-9b797284></div><h2 class="text" data-v-9b797284>SpringCloud</h2><div class="caret" role="button" aria-label="toggle section" tabindex="0" data-v-9b797284><svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false" viewbox="0 0 24 24" class="caret-icon" data-v-9b797284><path d="M9,19c-0.3,0-0.5-0.1-0.7-0.3c-0.4-0.4-0.4-1,0-1.4l5.3-5.3L8.3,6.7c-0.4-0.4-0.4-1,0-1.4s1-0.4,1.4,0l6,6c0.4,0.4,0.4,1,0,1.4l-6,6C9.5,18.9,9.3,19,9,19z"></path></svg></div></div><div class="items" data-v-9b797284><!--[--><div class="VPSidebarItem level-1 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/4%E3%80%81%E5%BE%AE%E6%9C%8D%E5%8A%A1/%E8%BF%9B%E9%98%B6.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>SpringCloud</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-1 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/4%E3%80%81%E5%BE%AE%E6%9C%8D%E5%8A%A1/%E5%BF%85%E5%A4%87/Sentinel.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>Sentinel</p><!--]--></a><!----></div><!----></div><!--]--></div></section></div><div class="group" data-v-845b8fc6><section class="VPSidebarItem level-0 collapsible collapsed" data-v-845b8fc6 data-v-9b797284><div class="item" role="button" tabindex="0" data-v-9b797284><div class="indicator" data-v-9b797284></div><h2 class="text" data-v-9b797284>SpringSecurity</h2><div class="caret" role="button" aria-label="toggle section" tabindex="0" data-v-9b797284><svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false" viewbox="0 0 24 24" class="caret-icon" data-v-9b797284><path d="M9,19c-0.3,0-0.5-0.1-0.7-0.3c-0.4-0.4-0.4-1,0-1.4l5.3-5.3L8.3,6.7c-0.4-0.4-0.4-1,0-1.4s1-0.4,1.4,0l6,6c0.4,0.4,0.4,1,0,1.4l-6,6C9.5,18.9,9.3,19,9,19z"></path></svg></div></div><div class="items" data-v-9b797284><!--[--><div class="VPSidebarItem level-1 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/4%E3%80%81%E5%BE%AE%E6%9C%8D%E5%8A%A1/SpringSecurity/%E5%9F%BA%E7%A1%80%E7%AF%87.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>SpringSecurity基础篇</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-1 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/4%E3%80%81%E5%BE%AE%E6%9C%8D%E5%8A%A1/SpringSecurity/%E8%BF%9B%E9%98%B6%E7%AF%87.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>SpringSecurity进阶篇</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-1 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/4%E3%80%81%E5%BE%AE%E6%9C%8D%E5%8A%A1/SpringSecurity/%E9%AB%98%E7%BA%A7%E7%AF%87.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>SpringSecurity高级篇</p><!--]--></a><!----></div><!----></div><!--]--></div></section></div><div class="group" data-v-845b8fc6><section class="VPSidebarItem level-0 collapsible collapsed" data-v-845b8fc6 data-v-9b797284><div class="item" role="button" tabindex="0" data-v-9b797284><div class="indicator" data-v-9b797284></div><h2 class="text" data-v-9b797284>Mybatis & MybatisPlus</h2><div class="caret" role="button" aria-label="toggle section" tabindex="0" data-v-9b797284><svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false" viewbox="0 0 24 24" class="caret-icon" data-v-9b797284><path d="M9,19c-0.3,0-0.5-0.1-0.7-0.3c-0.4-0.4-0.4-1,0-1.4l5.3-5.3L8.3,6.7c-0.4-0.4-0.4-1,0-1.4s1-0.4,1.4,0l6,6c0.4,0.4,0.4,1,0,1.4l-6,6C9.5,18.9,9.3,19,9,19z"></path></svg></div></div><div class="items" data-v-9b797284><!--[--><div class="VPSidebarItem level-1 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/Mybatis&amp;MybatisPlus/Mybatis.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>Mybatis</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-1 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/Mybatis&amp;MybatisPlus/MybatisPlus.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>MybatisPlus</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-1 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/Mybatis&amp;MybatisPlus/JPA.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>JPA</p><!--]--></a><!----></div><!----></div><!--]--></div></section></div><div class="group" data-v-845b8fc6><section class="VPSidebarItem level-0 collapsible collapsed" data-v-845b8fc6 data-v-9b797284><div class="item" role="button" tabindex="0" data-v-9b797284><div class="indicator" data-v-9b797284></div><h2 class="text" data-v-9b797284>Git & ChatGPT</h2><div class="caret" role="button" aria-label="toggle section" tabindex="0" data-v-9b797284><svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false" viewbox="0 0 24 24" class="caret-icon" data-v-9b797284><path d="M9,19c-0.3,0-0.5-0.1-0.7-0.3c-0.4-0.4-0.4-1,0-1.4l5.3-5.3L8.3,6.7c-0.4-0.4-0.4-1,0-1.4s1-0.4,1.4,0l6,6c0.4,0.4,0.4,1,0,1.4l-6,6C9.5,18.9,9.3,19,9,19z"></path></svg></div></div><div class="items" data-v-9b797284><!--[--><div class="VPSidebarItem level-1 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/5%E3%80%81%E8%BF%90%E7%BB%B4/Git.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>Git</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-1 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/5%E3%80%81%E8%BF%90%E7%BB%B4/Github.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>Github</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-1 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/5%E3%80%81%E8%BF%90%E7%BB%B4/ChatGPT.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>ChatGPT</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-1 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/5%E3%80%81%E8%BF%90%E7%BB%B4/Jenkins.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>Jenkins</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-1 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/5%E3%80%81%E8%BF%90%E7%BB%B4/Netty.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>Netty</p><!--]--></a><!----></div><!----></div><!--]--></div></section></div><div class="group" data-v-845b8fc6><section class="VPSidebarItem level-0 collapsible collapsed" data-v-845b8fc6 data-v-9b797284><div class="item" role="button" tabindex="0" data-v-9b797284><div class="indicator" data-v-9b797284></div><h2 class="text" data-v-9b797284>数据库</h2><div class="caret" role="button" aria-label="toggle section" tabindex="0" data-v-9b797284><svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false" viewbox="0 0 24 24" class="caret-icon" data-v-9b797284><path d="M9,19c-0.3,0-0.5-0.1-0.7-0.3c-0.4-0.4-0.4-1,0-1.4l5.3-5.3L8.3,6.7c-0.4-0.4-0.4-1,0-1.4s1-0.4,1.4,0l6,6c0.4,0.4,0.4,1,0,1.4l-6,6C9.5,18.9,9.3,19,9,19z"></path></svg></div></div><div class="items" data-v-9b797284><!--[--><section class="VPSidebarItem level-1 collapsible collapsed" data-v-9b797284 data-v-9b797284><div class="item" role="button" tabindex="0" data-v-9b797284><div class="indicator" data-v-9b797284></div><h3 class="text" data-v-9b797284>MySQL</h3><div class="caret" role="button" aria-label="toggle section" tabindex="0" data-v-9b797284><svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false" viewbox="0 0 24 24" class="caret-icon" data-v-9b797284><path d="M9,19c-0.3,0-0.5-0.1-0.7-0.3c-0.4-0.4-0.4-1,0-1.4l5.3-5.3L8.3,6.7c-0.4-0.4-0.4-1,0-1.4s1-0.4,1.4,0l6,6c0.4,0.4,0.4,1,0,1.4l-6,6C9.5,18.9,9.3,19,9,19z"></path></svg></div></div><div class="items" data-v-9b797284><!--[--><div class="VPSidebarItem level-2 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/2%E3%80%81%E6%95%B0%E6%8D%AE%E5%BA%93/MySQL/MySQL%E6%A0%B8%E5%BF%83/%E5%9F%BA%E7%A1%80.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>MySQL基础</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-2 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/2%E3%80%81%E6%95%B0%E6%8D%AE%E5%BA%93/MySQL/MySQL%E6%A0%B8%E5%BF%83/%E8%BF%9B%E9%98%B6.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>MySQL进阶</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-2 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/2%E3%80%81%E6%95%B0%E6%8D%AE%E5%BA%93/MySQL/MySQL%E6%A0%B8%E5%BF%83/%E4%BC%98%E5%8C%96.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>MySQL优化</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-2 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/2%E3%80%81%E6%95%B0%E6%8D%AE%E5%BA%93/MySQL/MySQL%E6%A0%B8%E5%BF%83/%E8%AE%BE%E8%AE%A1.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>MySQL设计</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-2 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/2%E3%80%81%E6%95%B0%E6%8D%AE%E5%BA%93/MySQL/MySQL%E6%A0%B8%E5%BF%83/%E8%BF%90%E7%BB%B4.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>MySQL运维</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-2 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/2%E3%80%81%E6%95%B0%E6%8D%AE%E5%BA%93/MySQL/%E5%88%86%E5%BA%93%E5%88%86%E8%A1%A8.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>分库分表</p><!--]--></a><!----></div><!----></div><!--]--></div></section><section class="VPSidebarItem level-1 collapsible collapsed" data-v-9b797284 data-v-9b797284><div class="item" role="button" tabindex="0" data-v-9b797284><div class="indicator" data-v-9b797284></div><h3 class="text" data-v-9b797284>Redis</h3><div class="caret" role="button" aria-label="toggle section" tabindex="0" data-v-9b797284><svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false" viewbox="0 0 24 24" class="caret-icon" data-v-9b797284><path d="M9,19c-0.3,0-0.5-0.1-0.7-0.3c-0.4-0.4-0.4-1,0-1.4l5.3-5.3L8.3,6.7c-0.4-0.4-0.4-1,0-1.4s1-0.4,1.4,0l6,6c0.4,0.4,0.4,1,0,1.4l-6,6C9.5,18.9,9.3,19,9,19z"></path></svg></div></div><div class="items" data-v-9b797284><!--[--><div class="VPSidebarItem level-2 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/2%E3%80%81%E6%95%B0%E6%8D%AE%E5%BA%93/Redis/Redis%E5%9F%BA%E7%A1%80.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>Redis基础</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-2 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/2%E3%80%81%E6%95%B0%E6%8D%AE%E5%BA%93/Redis/Redis%E4%BC%98%E5%8C%96.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>Redis优化</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-2 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/2%E3%80%81%E6%95%B0%E6%8D%AE%E5%BA%93/Redis/Redis%E5%8E%9F%E7%90%86.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>Redis原理</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-2 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/2%E3%80%81%E6%95%B0%E6%8D%AE%E5%BA%93/Redis/Redis%E9%AB%98%E7%BA%A7.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>Redis高级</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-2 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/2%E3%80%81%E6%95%B0%E6%8D%AE%E5%BA%93/Redis/Redis%E5%AE%9E%E6%88%98.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>Redis实战</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-2 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/2%E3%80%81%E6%95%B0%E6%8D%AE%E5%BA%93/Redis/%E6%9C%AC%E5%9C%B0%E7%BC%93%E5%AD%98.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>本地缓存</p><!--]--></a><!----></div><!----></div><!--]--></div></section><section class="VPSidebarItem level-1 collapsible collapsed" data-v-9b797284 data-v-9b797284><div class="item" role="button" tabindex="0" data-v-9b797284><div class="indicator" data-v-9b797284></div><h3 class="text" data-v-9b797284>MongoDB</h3><div class="caret" role="button" aria-label="toggle section" tabindex="0" data-v-9b797284><svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false" viewbox="0 0 24 24" class="caret-icon" data-v-9b797284><path d="M9,19c-0.3,0-0.5-0.1-0.7-0.3c-0.4-0.4-0.4-1,0-1.4l5.3-5.3L8.3,6.7c-0.4-0.4-0.4-1,0-1.4s1-0.4,1.4,0l6,6c0.4,0.4,0.4,1,0,1.4l-6,6C9.5,18.9,9.3,19,9,19z"></path></svg></div></div><div class="items" data-v-9b797284><!--[--><div class="VPSidebarItem level-2 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/2%E3%80%81%E6%95%B0%E6%8D%AE%E5%BA%93/MongoDB/%E5%9F%BA%E7%A1%80.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>MongoDB基础</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-2 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/2%E3%80%81%E6%95%B0%E6%8D%AE%E5%BA%93/MongoDB/%E6%95%B4%E5%90%88.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>MongoDB进阶</p><!--]--></a><!----></div><!----></div><!--]--></div></section><section class="VPSidebarItem level-1 collapsible collapsed" data-v-9b797284 data-v-9b797284><div class="item" role="button" tabindex="0" data-v-9b797284><div class="indicator" data-v-9b797284></div><h3 class="text" data-v-9b797284>ElasticSearch</h3><div class="caret" role="button" aria-label="toggle section" tabindex="0" data-v-9b797284><svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false" viewbox="0 0 24 24" class="caret-icon" data-v-9b797284><path d="M9,19c-0.3,0-0.5-0.1-0.7-0.3c-0.4-0.4-0.4-1,0-1.4l5.3-5.3L8.3,6.7c-0.4-0.4-0.4-1,0-1.4s1-0.4,1.4,0l6,6c0.4,0.4,0.4,1,0,1.4l-6,6C9.5,18.9,9.3,19,9,19z"></path></svg></div></div><div class="items" data-v-9b797284><!--[--><div class="VPSidebarItem level-2 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/2%E3%80%81%E6%95%B0%E6%8D%AE%E5%BA%93/ElasticSearch/1%E3%80%81ES%E5%9F%BA%E7%A1%80.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>ES基础</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-2 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/2%E3%80%81%E6%95%B0%E6%8D%AE%E5%BA%93/ElasticSearch/3%E3%80%81ES%E9%AB%98%E7%BA%A7.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>ES高级</p><!--]--></a><!----></div><!----></div><!--]--></div></section><div class="VPSidebarItem level-1 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/2%E3%80%81%E6%95%B0%E6%8D%AE%E5%BA%93/influxdb.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>InfluxDB</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-1 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/2%E3%80%81%E6%95%B0%E6%8D%AE%E5%BA%93/Neo4j.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>Neo4j</p><!--]--></a><!----></div><!----></div><!--]--></div></section></div><div class="group" data-v-845b8fc6><section class="VPSidebarItem level-0 collapsible collapsed" data-v-845b8fc6 data-v-9b797284><div class="item" role="button" tabindex="0" data-v-9b797284><div class="indicator" data-v-9b797284></div><h2 class="text" data-v-9b797284>高并发 & 秒杀 & 分布式</h2><div class="caret" role="button" aria-label="toggle section" tabindex="0" data-v-9b797284><svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false" viewbox="0 0 24 24" class="caret-icon" data-v-9b797284><path d="M9,19c-0.3,0-0.5-0.1-0.7-0.3c-0.4-0.4-0.4-1,0-1.4l5.3-5.3L8.3,6.7c-0.4-0.4-0.4-1,0-1.4s1-0.4,1.4,0l6,6c0.4,0.4,0.4,1,0,1.4l-6,6C9.5,18.9,9.3,19,9,19z"></path></svg></div></div><div class="items" data-v-9b797284><!--[--><div class="VPSidebarItem level-1 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/%E4%B8%89%E9%AB%98/%E5%88%86%E5%B8%83%E5%BC%8F.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>分布式理论</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-1 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/4%E3%80%81%E5%BE%AE%E6%9C%8D%E5%8A%A1/%E5%BF%85%E5%A4%87/%E5%88%86%E5%B8%83%E5%BC%8F%E9%94%81.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>分布式锁</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-1 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/%E4%B8%89%E9%AB%98/%E7%A7%92%E6%9D%80.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>秒杀</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-1 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/%E4%B8%89%E9%AB%98/%E9%AB%98%E5%8F%AF%E7%94%A8.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>高可用</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-1 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/%E4%B8%89%E9%AB%98/%E9%AB%98%E5%B9%B6%E5%8F%91.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>高并发</p><!--]--></a><!----></div><!----></div><!--]--></div></section></div><div class="group" data-v-845b8fc6><section class="VPSidebarItem level-0 collapsible collapsed" data-v-845b8fc6 data-v-9b797284><div class="item" role="button" tabindex="0" data-v-9b797284><div class="indicator" data-v-9b797284></div><h2 class="text" data-v-9b797284>云原生</h2><div class="caret" role="button" aria-label="toggle section" tabindex="0" data-v-9b797284><svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false" viewbox="0 0 24 24" class="caret-icon" data-v-9b797284><path d="M9,19c-0.3,0-0.5-0.1-0.7-0.3c-0.4-0.4-0.4-1,0-1.4l5.3-5.3L8.3,6.7c-0.4-0.4-0.4-1,0-1.4s1-0.4,1.4,0l6,6c0.4,0.4,0.4,1,0,1.4l-6,6C9.5,18.9,9.3,19,9,19z"></path></svg></div></div><div class="items" data-v-9b797284><!--[--><div class="VPSidebarItem level-1 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/%E4%BA%91%E5%8E%9F%E7%94%9F/Docker.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>Docker</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-1 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/%E4%BA%91%E5%8E%9F%E7%94%9F/K8S.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>K8S</p><!--]--></a><!----></div><!----></div><!--]--></div></section></div><div class="group" data-v-845b8fc6><section class="VPSidebarItem level-0 collapsible collapsed" data-v-845b8fc6 data-v-9b797284><div class="item" role="button" tabindex="0" data-v-9b797284><div class="indicator" data-v-9b797284></div><h2 class="text" data-v-9b797284>可视化 & 监控</h2><div class="caret" role="button" aria-label="toggle section" tabindex="0" data-v-9b797284><svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false" viewbox="0 0 24 24" class="caret-icon" data-v-9b797284><path d="M9,19c-0.3,0-0.5-0.1-0.7-0.3c-0.4-0.4-0.4-1,0-1.4l5.3-5.3L8.3,6.7c-0.4-0.4-0.4-1,0-1.4s1-0.4,1.4,0l6,6c0.4,0.4,0.4,1,0,1.4l-6,6C9.5,18.9,9.3,19,9,19z"></path></svg></div></div><div class="items" data-v-9b797284><!--[--><div class="VPSidebarItem level-1 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/%E5%8F%AF%E8%A7%86%E5%8C%96%20&amp;%20%E7%9B%91%E6%8E%A7/%E7%9B%91%E6%8E%A7%E5%9F%BA%E7%A1%80.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>监控基础</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-1 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/%E5%8F%AF%E8%A7%86%E5%8C%96%20&amp;%20%E7%9B%91%E6%8E%A7/%E7%9B%91%E6%8E%A7%E8%BF%9B%E9%98%B6.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>监控进阶</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-1 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/%E5%8F%AF%E8%A7%86%E5%8C%96%20&amp;%20%E7%9B%91%E6%8E%A7/%E5%8F%AF%E8%A7%86%E5%8C%96%E5%A4%A7%E5%B1%8F.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>可视化大屏</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-1 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/%E5%8F%AF%E8%A7%86%E5%8C%96%20&amp;%20%E7%9B%91%E6%8E%A7/Zabbix.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>Zabbix</p><!--]--></a><!----></div><!----></div><!--]--></div></section></div><div class="group" data-v-845b8fc6><section class="VPSidebarItem level-0 collapsible collapsed" data-v-845b8fc6 data-v-9b797284><div class="item" role="button" tabindex="0" data-v-9b797284><div class="indicator" data-v-9b797284></div><h2 class="text" data-v-9b797284>学前端</h2><div class="caret" role="button" aria-label="toggle section" tabindex="0" data-v-9b797284><svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false" viewbox="0 0 24 24" class="caret-icon" data-v-9b797284><path d="M9,19c-0.3,0-0.5-0.1-0.7-0.3c-0.4-0.4-0.4-1,0-1.4l5.3-5.3L8.3,6.7c-0.4-0.4-0.4-1,0-1.4s1-0.4,1.4,0l6,6c0.4,0.4,0.4,1,0,1.4l-6,6C9.5,18.9,9.3,19,9,19z"></path></svg></div></div><div class="items" data-v-9b797284><!--[--><section class="VPSidebarItem level-1 collapsible collapsed" data-v-9b797284 data-v-9b797284><div class="item" role="button" tabindex="0" data-v-9b797284><div class="indicator" data-v-9b797284></div><h3 class="text" data-v-9b797284>HTML+CSS</h3><div class="caret" role="button" aria-label="toggle section" tabindex="0" data-v-9b797284><svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false" viewbox="0 0 24 24" class="caret-icon" data-v-9b797284><path d="M9,19c-0.3,0-0.5-0.1-0.7-0.3c-0.4-0.4-0.4-1,0-1.4l5.3-5.3L8.3,6.7c-0.4-0.4-0.4-1,0-1.4s1-0.4,1.4,0l6,6c0.4,0.4,0.4,1,0,1.4l-6,6C9.5,18.9,9.3,19,9,19z"></path></svg></div></div><div class="items" data-v-9b797284><!--[--><div class="VPSidebarItem level-2 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/1%E3%80%81%E5%AD%A6%E5%89%8D%E7%AB%AF/1%E3%80%81HTML+CSS/HTML%E5%9F%BA%E7%A1%80.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>HTML基础</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-2 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/1%E3%80%81%E5%AD%A6%E5%89%8D%E7%AB%AF/1%E3%80%81HTML+CSS/CSS%E5%9F%BA%E7%A1%80.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>CSS基础</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-2 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/1%E3%80%81%E5%AD%A6%E5%89%8D%E7%AB%AF/1%E3%80%81HTML+CSS/%E7%BD%91%E9%A1%B5%E8%BF%9B%E9%98%B6.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>网页进阶</p><!--]--></a><!----></div><!----></div><!--]--></div></section><section class="VPSidebarItem level-1 collapsible collapsed" data-v-9b797284 data-v-9b797284><div class="item" role="button" tabindex="0" data-v-9b797284><div class="indicator" data-v-9b797284></div><h3 class="text" data-v-9b797284>JS+TS</h3><div class="caret" role="button" aria-label="toggle section" tabindex="0" data-v-9b797284><svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false" viewbox="0 0 24 24" class="caret-icon" data-v-9b797284><path d="M9,19c-0.3,0-0.5-0.1-0.7-0.3c-0.4-0.4-0.4-1,0-1.4l5.3-5.3L8.3,6.7c-0.4-0.4-0.4-1,0-1.4s1-0.4,1.4,0l6,6c0.4,0.4,0.4,1,0,1.4l-6,6C9.5,18.9,9.3,19,9,19z"></path></svg></div></div><div class="items" data-v-9b797284><!--[--><div class="VPSidebarItem level-2 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/1%E3%80%81%E5%AD%A6%E5%89%8D%E7%AB%AF/2%E3%80%81JS+TS/JS%20%E5%9F%BA%E7%A1%80.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>JS基础</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-2 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/1%E3%80%81%E5%AD%A6%E5%89%8D%E7%AB%AF/2%E3%80%81JS+TS/JS%20%E8%BF%9B%E9%98%B6.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>JS进阶</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-2 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/1%E3%80%81%E5%AD%A6%E5%89%8D%E7%AB%AF/2%E3%80%81JS+TS/ES6%20%E5%9F%BA%E7%A1%80.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>ES6基础</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-2 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/1%E3%80%81%E5%AD%A6%E5%89%8D%E7%AB%AF/2%E3%80%81JS+TS/ES6%20%E8%BF%9B%E9%98%B6.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>ES6进阶</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-2 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/1%E3%80%81%E5%AD%A6%E5%89%8D%E7%AB%AF/2%E3%80%81JS+TS/TypeScript.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>TS基础</p><!--]--></a><!----></div><!----></div><!--]--></div></section><section class="VPSidebarItem level-1 collapsible collapsed" data-v-9b797284 data-v-9b797284><div class="item" role="button" tabindex="0" data-v-9b797284><div class="indicator" data-v-9b797284></div><h3 class="text" data-v-9b797284>NodeJS</h3><div class="caret" role="button" aria-label="toggle section" tabindex="0" data-v-9b797284><svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false" viewbox="0 0 24 24" class="caret-icon" data-v-9b797284><path d="M9,19c-0.3,0-0.5-0.1-0.7-0.3c-0.4-0.4-0.4-1,0-1.4l5.3-5.3L8.3,6.7c-0.4-0.4-0.4-1,0-1.4s1-0.4,1.4,0l6,6c0.4,0.4,0.4,1,0,1.4l-6,6C9.5,18.9,9.3,19,9,19z"></path></svg></div></div><div class="items" data-v-9b797284><!--[--><div class="VPSidebarItem level-2 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/1%E3%80%81%E5%AD%A6%E5%89%8D%E7%AB%AF/4%E3%80%81Node/%E5%9F%BA%E7%A1%80%E7%AF%87.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>Node基础</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-2 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/1%E3%80%81%E5%AD%A6%E5%89%8D%E7%AB%AF/4%E3%80%81Node/%E8%BF%9B%E9%98%B6%E7%AF%87.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>Node进阶</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-2 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/1%E3%80%81%E5%AD%A6%E5%89%8D%E7%AB%AF/4%E3%80%81Node/%E9%A1%B9%E7%9B%AE%E5%AE%9E%E6%88%98.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>项目实战</p><!--]--></a><!----></div><!----></div><!--]--></div></section><section class="VPSidebarItem level-1 collapsible collapsed" data-v-9b797284 data-v-9b797284><div class="item" role="button" tabindex="0" data-v-9b797284><div class="indicator" data-v-9b797284></div><h3 class="text" data-v-9b797284>Vue</h3><div class="caret" role="button" aria-label="toggle section" tabindex="0" data-v-9b797284><svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false" viewbox="0 0 24 24" class="caret-icon" data-v-9b797284><path d="M9,19c-0.3,0-0.5-0.1-0.7-0.3c-0.4-0.4-0.4-1,0-1.4l5.3-5.3L8.3,6.7c-0.4-0.4-0.4-1,0-1.4s1-0.4,1.4,0l6,6c0.4,0.4,0.4,1,0,1.4l-6,6C9.5,18.9,9.3,19,9,19z"></path></svg></div></div><div class="items" data-v-9b797284><!--[--><div class="VPSidebarItem level-2 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/1%E3%80%81%E5%AD%A6%E5%89%8D%E7%AB%AF/3%E3%80%81Vue/Vue3/Vue3%E8%BF%9B%E9%98%B6.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>Vue3进阶</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-2 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/1%E3%80%81%E5%AD%A6%E5%89%8D%E7%AB%AF/3%E3%80%81Vue/Vue3/Vue3%E9%AB%98%E7%BA%A7.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>Vue3高级</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-2 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/1%E3%80%81%E5%AD%A6%E5%89%8D%E7%AB%AF/3%E3%80%81Vue/Vue3/Vue3%E6%96%B0%E8%AF%AD%E6%B3%95.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>Vue3新语法</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-2 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/1%E3%80%81%E5%AD%A6%E5%89%8D%E7%AB%AF/3%E3%80%81Vue/Vue2/Vue2%E9%A1%B9%E7%9B%AE.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>项目实战</p><!--]--></a><!----></div><!----></div><!--]--></div></section><section class="VPSidebarItem level-1 collapsible collapsed" data-v-9b797284 data-v-9b797284><div class="item" role="button" tabindex="0" data-v-9b797284><div class="indicator" data-v-9b797284></div><h3 class="text" data-v-9b797284>小程序</h3><div class="caret" role="button" aria-label="toggle section" tabindex="0" data-v-9b797284><svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false" viewbox="0 0 24 24" class="caret-icon" data-v-9b797284><path d="M9,19c-0.3,0-0.5-0.1-0.7-0.3c-0.4-0.4-0.4-1,0-1.4l5.3-5.3L8.3,6.7c-0.4-0.4-0.4-1,0-1.4s1-0.4,1.4,0l6,6c0.4,0.4,0.4,1,0,1.4l-6,6C9.5,18.9,9.3,19,9,19z"></path></svg></div></div><div class="items" data-v-9b797284><!--[--><div class="VPSidebarItem level-2 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/1%E3%80%81%E5%AD%A6%E5%89%8D%E7%AB%AF/5%E3%80%81%E5%B0%8F%E7%A8%8B%E5%BA%8F/%E5%BE%AE%E4%BF%A1%E5%B0%8F%E7%A8%8B%E5%BA%8F.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>小程序基础</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-2 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/1%E3%80%81%E5%AD%A6%E5%89%8D%E7%AB%AF/5%E3%80%81%E5%B0%8F%E7%A8%8B%E5%BA%8F/%E5%B0%8F%E7%A8%8B%E5%BA%8F%E4%BC%98%E5%8C%96.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>小程序优化</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-2 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/1%E3%80%81%E5%AD%A6%E5%89%8D%E7%AB%AF/5%E3%80%81%E5%B0%8F%E7%A8%8B%E5%BA%8F/uniapp.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>uniapp</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-2 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/1%E3%80%81%E5%AD%A6%E5%89%8D%E7%AB%AF/5%E3%80%81%E5%B0%8F%E7%A8%8B%E5%BA%8F/%E5%B0%8F%E7%A8%8B%E5%BA%8F%E9%A1%B9%E7%9B%AE.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>项目实战</p><!--]--></a><!----></div><!----></div><!--]--></div></section><!--]--></div></section></div><div class="group" data-v-845b8fc6><section class="VPSidebarItem level-0 collapsible has-active" data-v-845b8fc6 data-v-9b797284><div class="item" role="button" tabindex="0" data-v-9b797284><div class="indicator" data-v-9b797284></div><h2 class="text" data-v-9b797284>计算机基础</h2><div class="caret" role="button" aria-label="toggle section" tabindex="0" data-v-9b797284><svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false" viewbox="0 0 24 24" class="caret-icon" data-v-9b797284><path d="M9,19c-0.3,0-0.5-0.1-0.7-0.3c-0.4-0.4-0.4-1,0-1.4l5.3-5.3L8.3,6.7c-0.4-0.4-0.4-1,0-1.4s1-0.4,1.4,0l6,6c0.4,0.4,0.4,1,0,1.4l-6,6C9.5,18.9,9.3,19,9,19z"></path></svg></div></div><div class="items" data-v-9b797284><!--[--><div class="VPSidebarItem level-1 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/%E8%AE%A1%E7%AE%97%E6%9C%BA%E5%9F%BA%E7%A1%80/%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84/%E5%9F%BA%E7%A1%80%E7%AF%87.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>数据结构</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-1 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/%E8%AE%A1%E7%AE%97%E6%9C%BA%E5%9F%BA%E7%A1%80/%E8%AE%A1%E7%AE%97%E6%9C%BA%E5%9F%BA%E7%A1%80/%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>操作系统</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-1 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/%E8%AE%A1%E7%AE%97%E6%9C%BA%E5%9F%BA%E7%A1%80/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F/%E5%9F%BA%E7%A1%80%E7%AF%87.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>设计模式</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-1 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/%E8%AE%A1%E7%AE%97%E6%9C%BA%E5%9F%BA%E7%A1%80/%E8%AE%A1%E7%AE%97%E6%9C%BA%E7%BD%91%E7%BB%9C/%E7%BD%91%E7%BB%9C%E5%9F%BA%E7%A1%80.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>计算机网络</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-1 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/%E8%AE%A1%E7%AE%97%E6%9C%BA%E5%9F%BA%E7%A1%80/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F/UML.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>UML</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-1 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/%E8%AE%A1%E7%AE%97%E6%9C%BA%E5%9F%BA%E7%A1%80/%E7%AE%97%E6%B3%95/LeetCode.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>LeetCode</p><!--]--></a><!----></div><!----></div><!--]--></div></section></div><div class="group" data-v-845b8fc6><section class="VPSidebarItem level-0 collapsible collapsed" data-v-845b8fc6 data-v-9b797284><div class="item" role="button" tabindex="0" data-v-9b797284><div class="indicator" data-v-9b797284></div><h2 class="text" data-v-9b797284>项目实战</h2><div class="caret" role="button" aria-label="toggle section" tabindex="0" data-v-9b797284><svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false" viewbox="0 0 24 24" class="caret-icon" data-v-9b797284><path d="M9,19c-0.3,0-0.5-0.1-0.7-0.3c-0.4-0.4-0.4-1,0-1.4l5.3-5.3L8.3,6.7c-0.4-0.4-0.4-1,0-1.4s1-0.4,1.4,0l6,6c0.4,0.4,0.4,1,0,1.4l-6,6C9.5,18.9,9.3,19,9,19z"></path></svg></div></div><div class="items" data-v-9b797284><!--[--><section class="VPSidebarItem level-1 collapsible collapsed" data-v-9b797284 data-v-9b797284><div class="item" role="button" tabindex="0" data-v-9b797284><div class="indicator" data-v-9b797284></div><h3 class="text" data-v-9b797284>云尚办公</h3><div class="caret" role="button" aria-label="toggle section" tabindex="0" data-v-9b797284><svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false" viewbox="0 0 24 24" class="caret-icon" data-v-9b797284><path d="M9,19c-0.3,0-0.5-0.1-0.7-0.3c-0.4-0.4-0.4-1,0-1.4l5.3-5.3L8.3,6.7c-0.4-0.4-0.4-1,0-1.4s1-0.4,1.4,0l6,6c0.4,0.4,0.4,1,0,1.4l-6,6C9.5,18.9,9.3,19,9,19z"></path></svg></div></div><div class="items" data-v-9b797284><!--[--><div class="VPSidebarItem level-2 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/%E9%A1%B9%E7%9B%AE%E5%AE%9E%E6%88%98/%E4%BA%91%E5%B0%9A%E5%8A%9E%E5%85%AC/%E5%9F%BA%E7%A1%80%E7%AF%87.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>基础篇</p><!--]--></a><!----></div><!----></div><!--]--></div></section><section class="VPSidebarItem level-1 collapsible collapsed" data-v-9b797284 data-v-9b797284><div class="item" role="button" tabindex="0" data-v-9b797284><div class="indicator" data-v-9b797284></div><h3 class="text" data-v-9b797284>小兔鲜</h3><div class="caret" role="button" aria-label="toggle section" tabindex="0" data-v-9b797284><svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false" viewbox="0 0 24 24" class="caret-icon" data-v-9b797284><path d="M9,19c-0.3,0-0.5-0.1-0.7-0.3c-0.4-0.4-0.4-1,0-1.4l5.3-5.3L8.3,6.7c-0.4-0.4-0.4-1,0-1.4s1-0.4,1.4,0l6,6c0.4,0.4,0.4,1,0,1.4l-6,6C9.5,18.9,9.3,19,9,19z"></path></svg></div></div><div class="items" data-v-9b797284><!--[--><div class="VPSidebarItem level-2 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/%E9%A1%B9%E7%9B%AE%E5%AE%9E%E6%88%98/%E5%B0%8F%E5%85%94%E9%B2%9C/%E5%9F%BA%E7%A1%80%E7%AF%87.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>基础篇</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-2 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/%E9%A1%B9%E7%9B%AE%E5%AE%9E%E6%88%98/%E5%B0%8F%E5%85%94%E9%B2%9C/%E8%BF%9B%E9%98%B6%E7%AF%871.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>进阶篇1</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-2 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/%E9%A1%B9%E7%9B%AE%E5%AE%9E%E6%88%98/%E5%B0%8F%E5%85%94%E9%B2%9C/%E8%BF%9B%E9%98%B6%E7%AF%872.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>进阶篇2</p><!--]--></a><!----></div><!----></div><!--]--></div></section><section class="VPSidebarItem level-1 collapsible collapsed" data-v-9b797284 data-v-9b797284><div class="item" role="button" tabindex="0" data-v-9b797284><div class="indicator" data-v-9b797284></div><h3 class="text" data-v-9b797284>地图</h3><div class="caret" role="button" aria-label="toggle section" tabindex="0" data-v-9b797284><svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false" viewbox="0 0 24 24" class="caret-icon" data-v-9b797284><path d="M9,19c-0.3,0-0.5-0.1-0.7-0.3c-0.4-0.4-0.4-1,0-1.4l5.3-5.3L8.3,6.7c-0.4-0.4-0.4-1,0-1.4s1-0.4,1.4,0l6,6c0.4,0.4,0.4,1,0,1.4l-6,6C9.5,18.9,9.3,19,9,19z"></path></svg></div></div><div class="items" data-v-9b797284><!--[--><div class="VPSidebarItem level-2 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/%E9%A1%B9%E7%9B%AE%E5%AE%9E%E6%88%98/%E7%99%BE%E5%BA%A6%E5%9C%B0%E5%9B%BE/%E5%9F%BA%E7%A1%80%E7%AF%87.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>基础篇</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-2 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/%E9%A1%B9%E7%9B%AE%E5%AE%9E%E6%88%98/%E7%99%BE%E5%BA%A6%E5%9C%B0%E5%9B%BE/%E8%BF%9B%E9%98%B6%E7%AF%87.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>进阶篇</p><!--]--></a><!----></div><!----></div><!--]--></div></section><section class="VPSidebarItem level-1 collapsible collapsed" data-v-9b797284 data-v-9b797284><div class="item" role="button" tabindex="0" data-v-9b797284><div class="indicator" data-v-9b797284></div><h3 class="text" data-v-9b797284>苍穹外卖</h3><div class="caret" role="button" aria-label="toggle section" tabindex="0" data-v-9b797284><svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false" viewbox="0 0 24 24" class="caret-icon" data-v-9b797284><path d="M9,19c-0.3,0-0.5-0.1-0.7-0.3c-0.4-0.4-0.4-1,0-1.4l5.3-5.3L8.3,6.7c-0.4-0.4-0.4-1,0-1.4s1-0.4,1.4,0l6,6c0.4,0.4,0.4,1,0,1.4l-6,6C9.5,18.9,9.3,19,9,19z"></path></svg></div></div><div class="items" data-v-9b797284><!--[--><div class="VPSidebarItem level-2 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/%E9%A1%B9%E7%9B%AE%E5%AE%9E%E6%88%98/%E8%8B%8D%E7%A9%B9%E5%A4%96%E5%8D%96/%E8%BF%9B%E9%98%B6%E7%AF%87.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>进阶篇</p><!--]--></a><!----></div><!----></div><!--]--></div></section><section class="VPSidebarItem level-1 collapsible collapsed" data-v-9b797284 data-v-9b797284><div class="item" role="button" tabindex="0" data-v-9b797284><div class="indicator" data-v-9b797284></div><h3 class="text" data-v-9b797284>黑马头条</h3><div class="caret" role="button" aria-label="toggle section" tabindex="0" data-v-9b797284><svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false" viewbox="0 0 24 24" class="caret-icon" data-v-9b797284><path d="M9,19c-0.3,0-0.5-0.1-0.7-0.3c-0.4-0.4-0.4-1,0-1.4l5.3-5.3L8.3,6.7c-0.4-0.4-0.4-1,0-1.4s1-0.4,1.4,0l6,6c0.4,0.4,0.4,1,0,1.4l-6,6C9.5,18.9,9.3,19,9,19z"></path></svg></div></div><div class="items" data-v-9b797284><!--[--><div class="VPSidebarItem level-2 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/%E9%A1%B9%E7%9B%AE%E5%AE%9E%E6%88%98/%E9%BB%91%E9%A9%AC%E5%A4%B4%E6%9D%A1/%E5%9F%BA%E7%A1%80%E7%AF%87.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>基础篇</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-2 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/%E9%A1%B9%E7%9B%AE%E5%AE%9E%E6%88%98/%E9%BB%91%E9%A9%AC%E5%A4%B4%E6%9D%A1/%E8%BF%9B%E9%98%B6%E7%AF%87.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>进阶篇</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-2 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/%E9%A1%B9%E7%9B%AE%E5%AE%9E%E6%88%98/%E9%BB%91%E9%A9%AC%E5%A4%B4%E6%9D%A1/%E8%BF%9B%E9%98%B6%E7%AF%872.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>进阶篇2</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-2 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/%E9%A1%B9%E7%9B%AE%E5%AE%9E%E6%88%98/%E9%BB%91%E9%A9%AC%E5%A4%B4%E6%9D%A1/%E9%AB%98%E7%BA%A7%E7%AF%87.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>高级篇</p><!--]--></a><!----></div><!----></div><!--]--></div></section><div class="VPSidebarItem level-1 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/%E9%A1%B9%E7%9B%AE%E5%AE%9E%E6%88%98/%E6%94%AF%E4%BB%98.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>支付</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-1 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/%E9%A1%B9%E7%9B%AE%E5%AE%9E%E6%88%98/%E9%A1%B9%E7%9B%AE%E6%8E%A8%E8%8D%90.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>项目推荐</p><!--]--></a><!----></div><!----></div><!--]--></div></section></div><div class="group" data-v-845b8fc6><section class="VPSidebarItem level-0" data-v-845b8fc6 data-v-9b797284><!----><div class="items" data-v-9b797284><!--[--><div class="VPSidebarItem level-1 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/team.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>团队成员</p><!--]--></a><!----></div><!----></div><!--]--></div></section></div><!--]--><!--[--><!--]--></nav></aside><div class="VPContent has-sidebar" id="VPContent" data-v-255ec12d data-v-669faec9><div class="VPDoc has-sidebar has-aside" data-v-669faec9 data-v-6b87e69f><!--[--><!--]--><div class="container" data-v-6b87e69f><div class="aside" data-v-6b87e69f><div class="aside-curtain" data-v-6b87e69f></div><div class="aside-container" data-v-6b87e69f><div class="aside-content" data-v-6b87e69f><div class="VPDocAside" data-v-6b87e69f data-v-3f215769><!--[--><!--]--><!--[--><!--]--><div class="VPDocAsideOutline" data-v-3f215769 data-v-ff0f39c8><div class="content" data-v-ff0f39c8><div class="outline-marker" data-v-ff0f39c8></div><div class="outline-title" data-v-ff0f39c8>On this page</div><nav aria-labelledby="doc-outline-aria-label" data-v-ff0f39c8><span class="visually-hidden" id="doc-outline-aria-label" data-v-ff0f39c8> Table of Contents for current page </span><ul class="root" data-v-ff0f39c8 data-v-d0ee3533><!--[--><!--]--></ul></nav></div></div><!--[--><!--]--><div class="spacer" data-v-3f215769></div><!--[--><!--]--><!----><!--[--><!--]--><!--[--><!--]--></div></div></div></div><div class="content" data-v-6b87e69f><div class="content-container" data-v-6b87e69f><!--[--><!--]--><!----><main class="main" data-v-6b87e69f><div style="position:relative;" class="vp-doc _notebook_%E8%AE%A1%E7%AE%97%E6%9C%BA%E5%9F%BA%E7%A1%80_%E8%AE%A1%E7%AE%97%E6%9C%BA%E5%9F%BA%E7%A1%80_%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F" data-v-6b87e69f><div><p><a href="https://mp.weixin.qq.com/mp/appmsgalbum?__biz=MzUxODAzNDg4NQ==&amp;action=getalbum&amp;album_id=1408057986861416450&amp;scene=173&amp;from_msgid=2247485264&amp;from_itemidx=1&amp;count=3&amp;nolastread=1#wechat_redirect" target="_blank" rel="noreferrer">#图解系统 (qq.com)</a></p><h1 id="操作系统基础知识" tabindex="-1">操作系统基础知识 <a class="header-anchor" href="#操作系统基础知识" aria-label="Permalink to &quot;操作系统基础知识&quot;">​</a></h1><h2 id="一-操作系统基础" tabindex="-1">一 操作系统基础 <a class="header-anchor" href="#一-操作系统基础" aria-label="Permalink to &quot;一 操作系统基础&quot;">​</a></h2><h3 id="_1-1-什么是操作系统" tabindex="-1">1.1 什么是操作系统？ <a class="header-anchor" href="#_1-1-什么是操作系统" aria-label="Permalink to &quot;1.1 什么是操作系统？&quot;">​</a></h3><p>👨‍💻<strong>面试官</strong> ：先来个简单问题吧！<strong>什么是操作系统？</strong></p><p>🙋 <strong>我</strong> ：我通过以下四点向您介绍一下什么是操作系统吧！</p><ol><li><strong>操作系统（Operating System，简称OS）是管理计算机硬件与软件资源的程序，是计算机系统的内核与基石；</strong></li><li><strong>操作系统本质上是运行在计算机上的软件程序 ；</strong></li><li><strong>操作系统为用户提供一个与系统交互的操作界面 ；</strong></li><li><strong>操作系统分内核与外壳（我们可以把外壳理解成围绕着内核的应用程序，而内核可以理解为能直接操作硬件的程序）。</strong></li></ol><blockquote><p>关于内核多插一嘴：内核负责管理系统的进程、内存、设备驱动程序、文件和网络系统等等，决定着系统的性能和稳定性。是连接应用程序和硬件的桥梁。内核就是操作系统背后黑盒的核心。</p></blockquote><img src="https://edu-8673.oss-cn-beijing.aliyuncs.com/img2022.6.30/202207121523970.png" alt="image-20220712152339922" style="zoom:50%;"><h3 id="_1-2-系统调用" tabindex="-1">1.2 系统调用 <a class="header-anchor" href="#_1-2-系统调用" aria-label="Permalink to &quot;1.2 系统调用&quot;">​</a></h3><p>👨‍💻<strong>面试官</strong> ：**什么是系统调用呢？**能不能详细介绍一下。</p><p>🙋 <strong>我</strong> ：介绍系统调用之前，我们先来了解一下用户态和系统态。</p><p>根据进程访问资源的特点，我们可以把进程在系统上的运行分为两个级别：</p><ol><li>用户态(user mode) : 用户态运行的进程或可以直接读取用户程序的数据。</li><li>系统态(kernel mode):可以简单的理解系统态运行的进程或程序几乎可以访问计算机的任何资源，不受限制。</li></ol><p>说了用户态和系统态之后，那么什么是系统调用呢？</p><p>我们运行的程序基本都是运行在用户态，如果我们调用操作系统提供的系统态级别的子功能咋办呢？那就需要系统调用了！</p><p>也就是说在我们运行的用户程序中，凡是与系统态级别的资源有关的操作（如文件管理、进程控制、内存管理等)，都必须通过系统调用方式向操作系统提出服务请求，并由操作系统代为完成。</p><p>这些系统调用按功能大致可分为如下几类：</p><ul><li>设备管理。完成设备的请求或释放，以及设备启动等功能。</li><li>文件管理。完成文件的读、写、创建及删除等功能。</li><li>进程控制。完成进程的创建、撤销、阻塞及唤醒等功能。</li><li>进程通信。完成进程之间的消息传递或信号传递等功能。</li><li>内存管理。完成内存的分配、回收以及获取作业占用内存区大小及地址等功能。</li></ul><h2 id="二-进程和线程" tabindex="-1">二 进程和线程 <a class="header-anchor" href="#二-进程和线程" aria-label="Permalink to &quot;二 进程和线程&quot;">​</a></h2><h3 id="_2-1进程和线程的区别" tabindex="-1">2.1进程和线程的区别 <a class="header-anchor" href="#_2-1进程和线程的区别" aria-label="Permalink to &quot;2.1进程和线程的区别&quot;">​</a></h3><p>👨‍💻<strong>面试官</strong>: 好的！我明白了！那你再说一下：<strong>进程和线程的区别</strong>。</p><p>🙋 <strong>我：</strong> 好的！下图是 Java 内存区域，我们从 JVM 的角度来说一下线程和进程之间的关系吧！</p><blockquote><p>如果你对 Java 内存区域 (运行时数据区) 这部分知识不太了解的话可以阅读一下这篇文章：<a href="http://mp.weixin.qq.com/s?__biz=Mzg2OTA0Njk0OA==&amp;mid=2247485874&amp;idx=3&amp;sn=6f34ca32eee215385016190fcedc9ac6&amp;chksm=cea24679f9d5cf6f5331854fd4a0048cbf23b71c96e4c4ba3d74850695e8842b0a9477bf5070&amp;scene=21#wechat_redirect" target="_blank" rel="noreferrer">《可能是把 Java 内存区域讲的最清楚的一篇文章》</a></p></blockquote><img src="https://edu-8673.oss-cn-beijing.aliyuncs.com/img2022.6.30/202207121524299.png" alt="image-20220712152411222" style="zoom:50%;"><p>从上图可以看出：一个进程中可以有多个线程，多个线程共享进程的<strong>堆</strong>和**方法区 (JDK1.8 之后的元空间)*<em>资源，但是每个线程有自己的*<em>程序计数器</em></em>、<strong>虚拟机栈</strong> 和 <strong>本地方法栈</strong>。</p><p><strong>总结：</strong> 线程是进程划分成的更小的运行单位,一个进程在其执行的过程中可以产生多个线程。线程和进程最大的不同在于基本上各进程是独立的，而各线程则不一定，因为同一进程中的线程极有可能会相互影响。线程执行开销小，但不利于资源的管理和保护；而进程正相反。</p><h3 id="_2-2-进程有哪几种状态" tabindex="-1">2.2 进程有哪几种状态? <a class="header-anchor" href="#_2-2-进程有哪几种状态" aria-label="Permalink to &quot;2.2 进程有哪几种状态?&quot;">​</a></h3><p>👨‍💻<strong>面试官</strong> ：那你再说说<strong>进程有哪几种状态?</strong></p><p>🙋 <strong>我</strong> ：我们一般把进程大致分为 5 种状态，这一点和线程很像！</p><ul><li><strong>创建状态(new)</strong> ：进程正在被创建，尚未到就绪状态。</li><li><strong>就绪状态(ready)</strong> ：进程已处于准备运行状态，即进程获得了除了处理器之外的一切所需资源，一旦得到处理器资源(处理器分配的时间片)即可运行。</li><li><strong>运行状态(running)</strong> ：进程正在处理器上上运行(单核CPU下任意时刻只有一个进程处于运行状态)。</li><li><strong>阻塞状态(waiting)</strong> ：又称为等待状态，进程正在等待某一事件而暂停运行如等待某资源为可用或等待 IO 操作完成。即使处理器空闲，该进程也不能运行。</li><li><strong>结束状态(terminated)</strong> ：进程正在从系统中消失。可能是进程正常结束或其他原因中断退出运行。</li></ul><p><img src="https://mmbiz.qpic.cn/mmbiz_png/HrWw6ZuXCshZR1JxNCwxeic6wXW3JQfawUic7p7lpNkRsx6opQ2PY4AGddvv8aibOcUBMLAjibtRGHFAFeo1lE3XMg/640?wx_fmt=png&amp;wxfrom=5&amp;wx_lazy=1&amp;wx_co=1" alt="图片">process-state</p><h3 id="_2-3-进程间的通信方式" tabindex="-1">2.3 进程间的通信方式 <a class="header-anchor" href="#_2-3-进程间的通信方式" aria-label="Permalink to &quot;2.3 进程间的通信方式&quot;">​</a></h3><p>👨‍💻<strong>面试官</strong> ：<strong>进程间的通信常见的的有哪几种方式呢?</strong></p><p>🙋 <strong>我</strong> ：大概有 7 种常见的进程间的通信方式。</p><blockquote><p>下面这部分总结参考了:《进程间通信IPC (InterProcess Communication)》 这篇文章，推荐阅读，总结的非常不错。</p></blockquote><ol><li><strong>管道/匿名管道(Pipes)</strong> ：用于具有亲缘关系的父子进程间或者兄弟进程之间的通信。</li><li><strong>有名管道(Names Pipes)</strong> : 匿名管道由于没有名字，只能用于亲缘关系的进程间通信。为了克服这个缺点，提出了有名管道。有名管道严格遵循<strong>先进先出(first in first out)</strong>。有名管道以磁盘文件的方式存在，可以实现本机任意两个进程通信。</li><li><strong>信号(Signal)</strong> ：信号是一种比较复杂的通信方式，用于通知接收进程某个事件已经发生；</li><li><strong>消息队列(Message Queuing)</strong> ：消息队列是消息的链表,具有特定的格式,存放在内存中并由消息队列标识符标识。管道和消息队列的通信数据都是先进先出的原则。与管道（无名管道：只存在于内存中的文件；命名管道：存在于实际的磁盘介质或者文件系统）不同的是消息队列存放在内核中，只有在内核重启(即，操作系统重启)或者显示地删除一个消息队列时，该消息队列才会被真正的删除。消息队列可以实现消息的随机查询,消息不一定要以先进先出的次序读取,也可以按消息的类型读取.比FIFO更有优势。<strong>消息队列克服了信号承载信息量少，管道只能承载无格式字 节流以及缓冲区大小受限等缺。</strong></li><li><strong>信号量(Semaphores)</strong> ：信号量是一个计数器，用于多进程对共享数据的访问，信号量的意图在于进程间同步。这种通信方式主要用于解决与同步相关的问题并避免竞争条件。</li><li><strong>共享内存(Shared memory)</strong> ：使得多个进程可以访问同一块内存空间，不同进程可以及时看到对方进程中对共享内存中数据的更新。这种方式需要依靠某种同步操作，如互斥锁和信号量等。可以说这是最有用的进程间通信方式。</li><li><strong>套接字(Sockets)</strong> : 此方法主要用于在客户端和服务器之间通过网络进行通信。套接字是支持TCP/IP的网络通信的基本操作单元，可以看做是不同主机之间的进程进行双向通信的端点，简单的说就是通信的两方的一种约定，用套接字中的相关函数来完成通信过程。</li></ol><h3 id="_2-4-线程间的同步的方式" tabindex="-1">2.4 线程间的同步的方式 <a class="header-anchor" href="#_2-4-线程间的同步的方式" aria-label="Permalink to &quot;2.4 线程间的同步的方式&quot;">​</a></h3><p>👨‍💻<strong>面试官</strong> ：<strong>那线程间的同步的方式有哪些呢?</strong></p><p>🙋 <strong>我</strong> ：线程同步是两个或多个共享关键资源的线程的并发执行。应该同步线程以避免关键的资源使用冲突。操作系统一般有下面三种线程同步的方式：</p><ol><li><strong>互斥量(Mutex)</strong>：采用互斥对象机制，只有拥有互斥对象的线程才有访问公共资源的权限。因为互斥对象只有一个，所以可以保证公共资源不会被多个线程同时访问。比如 Java 中的 synchronized 关键词和各种 Lock 都是这种机制。</li><li><strong>信号量(Semphares)</strong> ：它允许同一时刻多个线程访问同一资源，但是需要控制同一时刻访问此资源的最大线程数量</li><li><strong>事件(Event)</strong> :Wait/Notify：通过通知操作的方式来保持多线程同步，还可以方便的实现多线程优先级的比较操</li></ol><h3 id="_2-5-进程的调度算法" tabindex="-1">2.5 进程的调度算法 <a class="header-anchor" href="#_2-5-进程的调度算法" aria-label="Permalink to &quot;2.5 进程的调度算法&quot;">​</a></h3><p>👨‍💻<strong>面试官</strong> ：<strong>你知道操作系统中进程的调度算法有哪些吗?</strong></p><p>🙋 <strong>我</strong> ：嗯嗯！这个我们大学的时候学过，是一个很重要的知识点！</p><p>为了确定首先执行哪个进程以及最后执行哪个进程以实现最大CPU利用率，计算机科学家已经定义了一些算法，它们是：</p><ul><li><strong>先到先服务(FCFS)调度算法</strong> : 从就绪队列中选择一个最先进入该队列的进程为之分配资源，使它立即执行并一直执行到完成或发生某事件而被阻塞放弃占用CPU时再重新调度。</li><li><strong>短作业优先(SJF)的调度算法</strong> : 从就绪队列中选出一个估计运行时间最短的进程为之分配资源，使它立即执行并一直执行到完成或发生某事件而被阻塞放弃占用CPU时再重新调度。</li><li><strong>时间片轮转调度算法</strong> : 时间片轮转调度是一种最古老，最简单，最公平且使用最广的算法，又称RR(Round robin)调度。每个进程被分配一个时间段，称作它的时间片，即该进程允许运行的时间。</li><li><strong>多级反馈队列调度算法</strong> ：前面介绍的几种进程调度的算法都有一定的局限性。如<strong>短进程优先的调度算法，仅照顾了短进程而忽略了长进程</strong> 。多级反馈队列调度算法既能使高优先级的作业得到响应又能使短作业（进程）迅速完成。，因而它是目前<strong>被公认的一种较好的进程调度算法</strong>，UNIX操作系统采取的便是这种调度算法。</li><li><strong>优先级调度</strong> ：为每个流程分配优先级，首先执行具有最高优先级的进程，依此类推。具有相同优先级的进程以FCFS方式执行。可以根据内存要求，时间要求或任何其他资源要求来确定优先级。</li></ul><h2 id="三-操作系统内存管理基础" tabindex="-1">三 操作系统内存管理基础 <a class="header-anchor" href="#三-操作系统内存管理基础" aria-label="Permalink to &quot;三 操作系统内存管理基础&quot;">​</a></h2><h3 id="_3-1-内存管理介绍" tabindex="-1">3.1 内存管理介绍 <a class="header-anchor" href="#_3-1-内存管理介绍" aria-label="Permalink to &quot;3.1 内存管理介绍&quot;">​</a></h3><p>👨‍💻 <strong>面试官</strong>: <strong>操作系统的内存管理主要是做什么？</strong></p><p>🙋 <strong>我：</strong> 操作系统的内存管理主要负责内存的分配与回收（malloc 函数：申请内存，free 函数：释放内存），另外地址转换也就是将逻辑地址转换成相应的物理地址等功能也是操作系统内存管理做的事情。</p><h3 id="_3-2-常见的几种内存管理机制" tabindex="-1">3.2 常见的几种内存管理机制 <a class="header-anchor" href="#_3-2-常见的几种内存管理机制" aria-label="Permalink to &quot;3.2 常见的几种内存管理机制&quot;">​</a></h3><p>👨‍💻 <strong>面试官</strong>: <strong>操作系统的内存管理机制了解吗？内存管理有哪几种方式?</strong></p><p>🙋 <strong>我：</strong> 这个在学习操作系统的时候有了解过。</p><p>简单分为<strong>连续分配管理方式</strong>和<strong>非连续分配管理方式</strong>这两种。连续分配管理方式是指为一个用户程序分配一个连续的内存空间，常见的如 <strong>块式管理</strong> 。同样地，非连续分配管理方式允许一个程序使用的内存分布在离散或者说不相邻的内存中，常见的如<strong>页式管理</strong> 和 <strong>段式管理</strong>。</p><ol><li><strong>块式管理</strong> ：远古时代的计算机操系统的内存管理方式。将内存分为几个固定大小的块，每个块中只包含一个进程。如果程序运行需要内存的话，操作系统就分配给它一块，如果程序运行只需要很小的空间的话，分配的这块内存很大一部分几乎被浪费了。这些在每个块中未被利用的空间，我们称之为碎片。</li><li><strong>页式管理</strong> ：把主存分为大小相等且固定的一页一页的形式，页较小，相对相比于块式管理的划分力度更大，提高了内存利用率，减少了碎片。页式管理通过页表对应逻辑地址和物理地址。</li><li><strong>段式管理</strong> ：页式管理虽然提高了内存利用率，但是页式管理其中的页实际并无任何实际意义。段式管理把主存分为一段段的，每一段的空间又要比一页的空间小很多 。但是，最重要的是段是有实际意义的，每个段定义了一组逻辑信息，例如,有主程序段 MAIN、子程序段 X、数据段 D 及栈段 S 等。段式管理通过段表对应逻辑地址和物理地址。</li></ol><p>👨‍💻<strong>面试官</strong> ：回答的还不错！不过漏掉了一个很重要的 <strong>段页式管理机制</strong> 。段页式管理机制结合了段式管理和页式管理的优点。简单来说段页式管理机制就是把主存先分成若干段，每个段又分成若干页，也就是说 <strong>段页式管理机制</strong> 中段与段之间以及段的内部的都是离散的。</p><p>🙋 <strong>我</strong> ：谢谢面试官！刚刚把这个给忘记了～</p><h3 id="_3-3-快表和多级页表" tabindex="-1">3.3 快表和多级页表 <a class="header-anchor" href="#_3-3-快表和多级页表" aria-label="Permalink to &quot;3.3 快表和多级页表&quot;">​</a></h3><p>👨‍💻<strong>面试官</strong> ：页表管理机制中有两个很重要的概念：快表和多级页表，这两个东西分别解决了页表管理中很重要的两个问题。你给我简单介绍一下吧！</p><p>🙋 <strong>我</strong> ：在分页内存管理中，很重要的两点是：</p><ol><li>虚拟地址到物理地址的转换要快。</li><li>解决虚拟地址空间大，页表也会很大的问题。</li></ol><h4 id="快表" tabindex="-1">快表 <a class="header-anchor" href="#快表" aria-label="Permalink to &quot;快表&quot;">​</a></h4><p>为了解决虚拟地址到物理地址的转换速度，操作系统在 <strong>页表方案</strong> 基础之上引入了 <strong>快表</strong> 来加速虚拟地址到物理地址的转换。我们可以把块表理解为一种特殊的高速缓冲存储器（Cache），其中的内容是页表的一部分或者全部内容。作为页表的Cache，它的作用与页表相似，但是提高了访问速率。由于采用页表做地址转换，读写内存数据时CPU要访问两次主存。有了快表，有时只要访问一次高速缓冲存储器，一次主存，这样可加速查找并提高指令执行速度。</p><p>使用快表之后的地址转换流程是这样的：</p><ol><li>根据虚拟地址中的页号查快表；</li><li>如果该页在快表中，直接从快表中读取相应的物理地址；</li><li>如果该页不在快表中，就访问内存中的页表，再从页表中得到物理地址，同时将页表中的该映射表项添加到快表中；</li><li>当快表填满后，又要登记新页时，就按照一定的淘汰策略淘汰掉快表中的一个页。</li></ol><p>看完了之后你会发现快表和我们平时经常在我们开发的系统使用的缓存（比如 Redis）很像，的确是这样的，操作系统中的很多思想、很多经典的算法，你都可以在我们日常开发使用的各种工具或者框架中找到它们的影子。</p><h4 id="多级页表" tabindex="-1">多级页表 <a class="header-anchor" href="#多级页表" aria-label="Permalink to &quot;多级页表&quot;">​</a></h4><p>引入多级页表的主要目的是为了避免把全部页表一直放在内存中占用过多空间，特别是那些根本就不需要的页表就不需要保留在内存中。多级页表属于时间换空间的典型场景，具体可以查看下面这篇文章</p><ul><li>多级页表如何节约内存：<a href="https://www.polarxiong.com/archives/%E5%A4%9A%E7%BA%A7%E9%A1%B5%E8%A1%A8%E5%A6%82%E4%BD%95%E8%8A%82%E7%BA%A6%E5%86%85%E5%AD%98.html" target="_blank" rel="noreferrer">https://www.polarxiong.com/archives/多级页表如何节约内存.html</a></li></ul><h4 id="总结" tabindex="-1">总结 <a class="header-anchor" href="#总结" aria-label="Permalink to &quot;总结&quot;">​</a></h4><p>为了提高内存的空间性能，提出了多级页表的概念；但是提到空间性能是以浪费时间性能为基础的，因此为了补充损失的时间性能，提出了快表（即TLB）的概念。不论是快表还是多级页表实际上都利用到了程序的局部性原理，局部性原理在后面的虚拟内存这部分会介绍到。</p><h3 id="_3-4-分页机制和分段机制的共同点和区别" tabindex="-1">3.4 分页机制和分段机制的共同点和区别 <a class="header-anchor" href="#_3-4-分页机制和分段机制的共同点和区别" aria-label="Permalink to &quot;3.4 分页机制和分段机制的共同点和区别&quot;">​</a></h3><p>👨‍💻<strong>面试官</strong> ：<strong>分页机制和分段机制有哪些共同点和区别呢？</strong></p><p>🙋 <strong>我</strong> ：</p><ol><li><p><strong>共同点</strong> ：</p></li><li><ul><li>分页机制和分段机制都是为了提高内存利用率，较少内存碎片。</li><li>页和段都是离散存储的，所以两者都是离散分配内存的方式。但是，每个页和段中的内存是连续的。</li></ul></li><li><p><strong>区别</strong> ：</p></li><li><ul><li>页的大小是固定的，由操作系统决定；而段的大小不固定，取决于我们当前运行的程序。</li><li>分页仅仅是为了满足操作系统内存管理的需求，而段是逻辑信息的单位，在程序中可以体现为代码段，数据段，能够更好满足用户的需要。</li></ul></li></ol><h3 id="_3-5-逻辑-虚拟-地址和物理地址" tabindex="-1">3.5 逻辑(虚拟)地址和物理地址 <a class="header-anchor" href="#_3-5-逻辑-虚拟-地址和物理地址" aria-label="Permalink to &quot;3.5 逻辑(虚拟)地址和物理地址&quot;">​</a></h3><p>👨‍💻<strong>面试官</strong> ：你刚刚还提到了<strong>逻辑地址和物理地址</strong>这两个概念，我不太清楚，你能为我解释一下不？</p><p>🙋 <strong>我：</strong> em...好的嘛！我们编程一般只有可能和逻辑地址打交道，比如在 C 语言中，指针里面存储的数值就可以理解成为内存里的一个地址，这个地址也就是我们说的逻辑地址，逻辑地址由操作系统决定。物理地址指的是真实物理内存中地址，更具体一点来说就是内存地址寄存器中的地址。物理地址是内存单元真正的地址。</p><h3 id="_3-6-cpu寻址了解吗-为什么需要虚拟地址空间" tabindex="-1">3.6 CPU寻址了解吗?为什么需要虚拟地址空间? <a class="header-anchor" href="#_3-6-cpu寻址了解吗-为什么需要虚拟地址空间" aria-label="Permalink to &quot;3.6 CPU寻址了解吗?为什么需要虚拟地址空间?&quot;">​</a></h3><p>👨‍💻<strong>面试官</strong> ：<strong>CPU寻址了解吗?为什么需要虚拟地址空间?</strong></p><p>🙋 <strong>我</strong> ：这部分我真不清楚！</p><p>于是面试完之后我默默去查阅了相关文档！留下了没有技术的泪水。。。</p><blockquote><p>这部分内容参考了Microsoft官网的介绍，地址：<a href="https://msdn.microsoft.com/zh-cn/library/windows/hardware/hh439648(v=vs.85).aspx" target="_blank" rel="noreferrer">https://msdn.microsoft.com/zh-cn/library/windows/hardware/hh439648(v=vs.85).aspx</a></p></blockquote><p>现代处理器使用的是一种称为 <strong>虚拟寻址(Virtual Addressing)</strong> 的寻址方式。<strong>使用虚拟寻址，CPU需要将虚拟地址翻译成物理地址，这样才能访问到真实的物理内存。</strong> 实际上完成虚拟地址转换为物理地址转换的硬件是 CPU 中含有一个被称为 **内存管理单元（Memory Management Unit, MMU）**的硬件。如下图所示：</p><p><img src="https://mmbiz.qpic.cn/mmbiz_png/HrWw6ZuXCshZR1JxNCwxeic6wXW3JQfawkSUh5LBZ1HMczBk6OYjOeyU6khAjluJHqJ4lcSu8eaFibB19micicEwrQ/640?wx_fmt=png&amp;wxfrom=5&amp;wx_lazy=1&amp;wx_co=1" alt="图片">MMU_principle_updated</p><p><strong>为什么要有虚拟地址空间呢？</strong></p><p>先从没有虚拟地址空间的时候说起吧！没有虚拟地址空间的时候，<strong>程序都是直接访问和操作的都是物理内存</strong> 。但是这样有什么问题呢？</p><ol><li>用户程序可以访问任意内存，寻址内存的每个字节，这样就很容易（有意或者无意）破坏操作系统，造成操作系统崩溃。</li><li>想要同时运行多个程序特别困难，比如你想同时运行一个微信和一个QQ音乐都不行。为什么呢？举个简单的例子：微信在运行的时候给内存地址1xxx赋值后，QQ音乐也同样给内存地址1xxx赋值，那么QQ音乐对内存的赋值就会覆盖微信之前所赋的值，这就造成了微信这个程序就会崩溃。</li></ol><p><strong>总结来说：如果直接把物理地址暴露出来的话会带来严重问题，比如可能对操作系统造成伤害以及给同时运行多个程序造成困难。</strong></p><p>通过虚拟地址访问内存有以下优势：</p><ul><li>程序可以使用一系列相邻的虚拟地址来访问物理内存中不相邻的大内存缓冲区。</li><li>程序可以使用一系列虚拟地址来访问大于可用物理内存的内存缓冲区。当物理内存的供应量变小时，内存管理器会将物理内存页（通常大小为 4 KB）保存到磁盘文件。数据或代码页会根据需要在物理内存与磁盘之间移动。</li><li>不同进程使用的虚拟地址彼此隔离。一个进程中的代码无法更改正在由另一进程或操作系统使用的物理内存。</li></ul><h2 id="四-虚拟内存" tabindex="-1">四 虚拟内存 <a class="header-anchor" href="#四-虚拟内存" aria-label="Permalink to &quot;四 虚拟内存&quot;">​</a></h2><h3 id="_4-1-什么是虚拟内存-virtual-memory" tabindex="-1">4.1 什么是虚拟内存(Virtual Memory)? <a class="header-anchor" href="#_4-1-什么是虚拟内存-virtual-memory" aria-label="Permalink to &quot;4.1 什么是虚拟内存(Virtual Memory)?&quot;">​</a></h3><p>👨‍💻<strong>面试官</strong> ：再问你一个常识性的问题！<strong>什么是虚拟内存(Virtual Memory)?</strong></p><p>🙋 <strong>我</strong> ：这个在我们平时使用电脑特别是 Windows 系统的时候太常见了。很多时候我们使用点开了很多占内存的软件，这些软件占用的内存可能已经远远超出了我们电脑本身具有的物理内存。<strong>为什么可以这样呢？</strong> 正是因为 <strong>虚拟内存</strong> 的存在，通过 <strong>虚拟内存</strong> 可以让程序可以拥有超过系统物理内存大小的可用内存空间。另外，<strong>虚拟内存为每个进程提供了一个一致的、私有的地址空间，它让每个进程产生了一种自己在独享主存的错觉（每个进程拥有一片连续完整的内存空间）</strong>。这样会更加有效地管理内存并减少出错。</p><p><strong>虚拟内存</strong>是计算机系统内存管理的一种技术，我们可以手动设置自己电脑的虚拟内存。不要单纯认为虚拟内存只是“使用硬盘空间来扩展内存“的技术。<strong>虚拟内存的重要意义是它定义了一个连续的虚拟地址空间</strong>，并且 <strong>把内存扩展到硬盘空间</strong>。推荐阅读：《虚拟内存的那点事儿》</p><p>维基百科中有几句话是这样介绍虚拟内存的。</p><blockquote><p><strong>虚拟内存</strong> 使得应用程序认为它拥有连续的可用的内存（一个连续完整的地址空间），而实际上，它通常是被分隔成多个物理内存碎片，还有部分暂时存储在外部磁盘存储器上，在需要时进行数据交换。与没有使用虚拟内存技术的系统相比，使用这种技术的系统使得大型程序的编写变得更容易，对真正的物理内存（例如RAM）的使用也更有效率。目前，大多数操作系统都使用了虚拟内存，如Windows家族的“虚拟内存”；Linux的“交换空间”等。From:<a href="https://zh.wikipedia.org/wiki/%E8%99%9A%E6%8B%9F%E5%86%85%E5%AD%98" target="_blank" rel="noreferrer">https://zh.wikipedia.org/wiki/虚拟内存</a></p></blockquote><h3 id="_4-2-局部性原理" tabindex="-1">4.2 局部性原理 <a class="header-anchor" href="#_4-2-局部性原理" aria-label="Permalink to &quot;4.2 局部性原理&quot;">​</a></h3><p>👨‍💻<strong>面试官</strong> ：要想更好地理解虚拟内存技术，必须要知道计算机中著名的<strong>局部性原理</strong>。另外，局部性原理既适用于程序结构，也适用于数据结构，是非常重要的一个概念。</p><p>🙋 <strong>我</strong> ：局部性原理是虚拟内存技术的基础，正是因为程序运行具有局部性原理，才可以只装入部分程序到内存就开始运行。</p><blockquote><p>以下内容摘自《计算机操作系统教程》 第4章存储器管理。</p></blockquote><p>早在1968年的时候，就有人指出我们的程序在执行的时候往往呈现局部性规律，也就是说在某个较短的时间段内，程序执行局限于某一小部分，程序访问的存储空间也局限于某个区域。</p><p>局部性原理表现在以下两个方面：</p><ol><li><strong>时间局部性</strong> ：如果程序中的某条指令一旦执行，不久以后该指令可能再次执行；如果某数据被访问过，不久以后该数据可能再次被访问。产生时间局部性的典型原因，是由于在程序中存在着大量的循环操作。</li><li><strong>空间局部性</strong> ：一旦程序访问了某个存储单元，在不久之后，其附近的存储单元也将被访问，即程序在一段时间内所访问的地址，可能集中在一定的范围之内，这是因为指令通常是顺序存放、顺序执行的，数据也一般是以向量、数组、表等形式簇聚存储的。</li></ol><p>时间局部性是通过将近来使用的指令和数据保存到高速缓存存储器中，并使用高速缓存的层次结构实现。空间局部性通常是使用较大的高速缓存，并将预取机制集成到高速缓存控制逻辑中实现。虚拟内存技术实际上就是建立了 “内存一外存”的两级存储器的结构，利用局部性原理实现髙速缓存。</p><h3 id="_4-3-虚拟存储器" tabindex="-1">4.3 虚拟存储器 <a class="header-anchor" href="#_4-3-虚拟存储器" aria-label="Permalink to &quot;4.3 虚拟存储器&quot;">​</a></h3><p>👨‍💻<strong>面试官</strong> ：都说了虚拟内存了。你再讲讲<strong>虚拟存储器</strong>把！</p><p>🙋 <strong>我</strong> ：</p><blockquote><p>这部分内容来自：王道考研操作系统知识点整理。</p></blockquote><p>基于局部性原理，在程序装入时，可以将程序的一部分装入内存，而将其他部分留在外存，就可以启动程序执行。由于外存往往比内存大很多，所以我们运行的软件的内存大小实际上是可以比计算机系统实际的内存大小大的。在程序执行过程中，当所访问的信息不在内存时，由操作系统将所需要的部分调入内存，然后继续执行程序。另一方面，操作系统将内存中暂时不使用的内容换到外存上，从而腾出空间存放将要调入内存的信息。这样，计算机好像为用户提供了一个比实际内存大的多的存储器——<strong>虚拟存储器</strong>。</p><p>实际上，我觉得虚拟内存同样是一种时间换空间的策略，你用 CPU 的计算时间，页的调入调出花费的时间，换来了一个虚拟的更大的空间来支持程序的运行。不得不感叹，程序世界几乎不是时间换空间就是空间换时间。</p><h3 id="_4-4-虚拟内存的技术实现" tabindex="-1">4.4 虚拟内存的技术实现 <a class="header-anchor" href="#_4-4-虚拟内存的技术实现" aria-label="Permalink to &quot;4.4 虚拟内存的技术实现&quot;">​</a></h3><p>👨‍💻<strong>面试官</strong> ：<strong>虚拟内存技术的实现呢？</strong></p><p>🙋 <strong>我</strong> ：<strong>虚拟内存的实现需要建立在离散分配的内存管理方式的基础上。</strong> 虚拟内存的实现有以下三种方式：</p><ol><li><strong>请求分页存储管理</strong> ：建立在基本分页系统基础之上，为了支持虚拟存储器功能而增加了请求调页功能和页面置换功能。请求分页是目前最常用的一种实现虚拟存储器的方法。</li><li><strong>请求分段存储管理</strong> ：</li><li><strong>请求段页式存储管理</strong> ：</li></ol><p>不管是上面那种实现方式，我们一般都需要：</p><ol><li>一定容量的内存和外存：在载入程序的时候，只需要将程序的一部分装入内存，而将其他部分留在外存，然后程序就可以执行了；</li><li><strong>缺页中断</strong>：如果<strong>需执行的指令或访问的数据尚未在内存</strong>（称为缺页或缺段），则由处理器通知操作系统将相应的页面或段<strong>调入到内存</strong>，然后继续执行程序；</li><li><strong>虚拟地址空间</strong> ：逻辑地址到物理地址的变换。</li></ol><h3 id="_4-5-页面置换算法" tabindex="-1">4.5 页面置换算法 <a class="header-anchor" href="#_4-5-页面置换算法" aria-label="Permalink to &quot;4.5 页面置换算法&quot;">​</a></h3><p>👨‍💻<strong>面试官</strong> ：虚拟内存管理很重要的一个概念就是页面置换算法。那你说一下 <strong>页面置换算法的作用?常见的页面置换算法有哪些?</strong></p><p>🙋 <strong>我</strong> ：</p><blockquote><p>这个题目经常作为笔试题出现，网上已经给出了很不错的回答，我这里只是总结整理了一下。</p></blockquote><p>地址映射过程中，若在页面中发现所要访问的页面不在内存中，则发生缺页中断 。</p><blockquote><p><strong>缺页中断</strong> 就是要访问的<strong>页</strong>不在主存，需要操作系统将其调入主存后再进行访问。在这个时候，被内存映射的文件实际上成了一个分页交换文件。</p></blockquote><p>当发生缺页中断时，如果当前内存中并没有空闲的页面，操作系统就必须在内存选择一个页面将其移出内存，以便为即将调入的页面让出空间。用来选择淘汰哪一页的规则叫做页面置换算法，我们可以把页面置换算法看成是淘汰页面的规则。</p><ul><li><strong>OPT页面置换算法（最佳页面置换算法）</strong> ：理想情况，不可能实现，一般作为衡量其他置换算法的方法。</li><li><strong>FIFO页面置换算法（先进先出页面置换算法）</strong> : 总是淘汰最先进入内存的页面，即选择在内存中驻留时间最久的页面进行淘汰。</li><li><strong>LRU页面置换算法（最近未使用页面置换算法）</strong> ：LRU（Least Currently Used）算法赋予每个页面一个访问字段，用来记录一个页面自上次被访问以来所经历的时间T，当须淘汰一个页面时，选择现有页面中其T值最大的，即最近最久未使用的页面予以淘汰。</li><li><strong>LFU页面置换算法（最少使用页面排序算法）</strong> : LFU（Least Frequently Used）算法会让系统维护一个按最近一次访问时间排序的页面链表，链表首节点是最近刚刚使用过的页面，链表尾节点是最久未使用的页面。访问内存时，找到相应页面，并把它移到链表之首。缺页时，置换链表尾节点的页面。也就是说内存内使用越频繁的页面，被保留的时间也相对越长。</li></ul><h1 id="进程和线程基础" tabindex="-1">进程和线程基础 <a class="header-anchor" href="#进程和线程基础" aria-label="Permalink to &quot;进程和线程基础&quot;">​</a></h1><p><a href="https://mp.weixin.qq.com/s?__biz=Mzg3NzU5NTIwNg==&amp;mid=2247488036&amp;idx=1&amp;sn=114d6be8bd03d6e1d982dda5ed2829a3&amp;chksm=cf21cd0df856441b25bfe0481ade37ecd9b388ec0807bef9c10508bded7aee7ec8650a2f5411&amp;mpshare=1&amp;scene=23&amp;srcid=0718IOWQrMoSOQCakNcIa71b&amp;sharer_sharetime=1658106362753&amp;sharer_shareid=29b8a04db1dbd975e3bf4e9f47e7ac67#rd" target="_blank" rel="noreferrer">进程和线程基础知识全家桶，30 张图一套带走 (qq.com)</a></p><h2 id="前言" tabindex="-1">前言 <a class="header-anchor" href="#前言" aria-label="Permalink to &quot;前言&quot;">​</a></h2><blockquote><p>先来看看一则小故事</p></blockquote><p>我们写好的一行行代码，为了让其工作起来，我们还得把它送进城（<strong>进程</strong>）里，那既然进了城里，那肯定不能胡作非为了。</p><p>城里人有城里人的规矩，城中有个专门管辖你们的城管（<strong>操作系统</strong>），人家让你休息就休息，让你工作就工作，毕竟摊位（<strong>CPU</strong>）就一个，每个人都要占这个摊位来工作，城里要工作的人多着去了。</p><p>所以城管为了公平起见，它使用一种策略（<strong>调度</strong>）方式，给每个人一个固定的工作时间（<strong>时间片</strong>），时间到了就会通知你去休息而换另外一个人上场工作。</p><p>另外，在休息时候你也不能偷懒，要记住工作到哪了，不然下次到你工作了，你忘记工作到哪了，那还怎么继续？</p><p>有的人，可能还进入了县城（<strong>线程</strong>）工作，这里相对轻松一些，在休息的时候，要记住的东西相对较少，而且还能共享城里的资源。</p><blockquote><p>“哎哟，难道本文内容是进程和线程？”</p></blockquote><p>可以，聪明的你猜出来了，也不枉费我瞎编乱造的故事了。</p><p>进程和线程对于写代码的我们，真的天天见、日日见了，但见的多不代表你就熟悉它们，比如简单问你一句，你知道它们的工作原理和区别吗？</p><p>不知道没关系，今天就要跟大家讨论<strong>操作系统的进程和线程</strong>。</p><img src="https://edu-8673.oss-cn-beijing.aliyuncs.com/img2022.6.30/202207211035904.png" alt="image-20220721103500798" style="zoom:50%;"><h2 id="进程" tabindex="-1">进程 <a class="header-anchor" href="#进程" aria-label="Permalink to &quot;进程&quot;">​</a></h2><p>我们编写的代码只是一个存储在硬盘的静态文件，通过编译后就会生成二进制可执行文件，当我们运行这个可执行文件后，它会被装载到内存中，接着 CPU 会执行程序中的每一条指令，那么这个<strong>运行中的程序，就被称为「进程」</strong>。</p><p>现在我们考虑有一个会读取硬盘文件数据的程序被执行了，那么当运行到读取文件的指令时，就会去从硬盘读取数据，但是硬盘的读写速度是非常慢的，那么在这个时候，如果 CPU 傻傻的等硬盘返回数据的话，那 CPU 的利用率是非常低的。</p><p>做个类比，你去煮开水时，你会傻傻的等水壶烧开吗？很明显，小孩也不会傻等。我们可以在水壶烧开之前去做其他事情。当水壶烧开了，我们自然就会听到“嘀嘀嘀”的声音，于是再把烧开的水倒入到水杯里就好了。</p><p>所以，当进程要从硬盘读取数据时，CPU 不需要阻塞等待数据的返回，而是去执行另外的进程。当硬盘数据返回时，CPU 会收到个<strong>中断</strong>，于是 CPU 再继续运行这个进程。</p><img src="https://edu-8673.oss-cn-beijing.aliyuncs.com/img2022.6.30/202207211035600.png" alt="image-20220721103533530" style="zoom:67%;"><p>这种<strong>多个程序、交替执行</strong>的思想，就有 CPU 管理多个进程的初步想法。</p><p>对于一个支持多进程的系统，CPU 会从一个进程快速切换至另一个进程，其间每个进程各运行几十或几百个毫秒。</p><p>虽然单核的 CPU 在某一个瞬间，只能运行一个进程。但在 1 秒钟期间，它可能会运行多个进程，这样就产生<strong>并行的错觉</strong>，实际上这是<strong>并发</strong>。</p><h3 id="并发和并行区别" tabindex="-1">并发和并行区别 <a class="header-anchor" href="#并发和并行区别" aria-label="Permalink to &quot;并发和并行区别&quot;">​</a></h3><img src="https://edu-8673.oss-cn-beijing.aliyuncs.com/img2022.6.30/202207211035442.png" alt="image-20220721103557356" style="zoom:67%;"><h3 id="进程与程序关系" tabindex="-1">进程与程序关系 <a class="header-anchor" href="#进程与程序关系" aria-label="Permalink to &quot;进程与程序关系&quot;">​</a></h3><p>到了晚饭时间，一对小情侣肚子都咕咕叫了，于是男生见机行事，就想给女生做晚饭，所以他就在网上找了辣子鸡的菜谱，接着买了一些鸡肉、辣椒、香料等材料，然后边看边学边做这道菜。</p><img src="https://edu-8673.oss-cn-beijing.aliyuncs.com/img2022.6.30/202207211036638.png" alt="image-20220721103612580" style="zoom:80%;"><p>突然，女生说她想喝可乐，那么男生只好把做菜的事情暂停一下，并在手机菜谱标记做到哪一个步骤，把状态信息记录了下来。</p><p>然后男生听从女生的指令，跑去下楼买了一瓶冰可乐后，又回到厨房继续做菜。</p><p><strong>这体现了，CPU 可以从一个进程（做菜）切换到另外一个进程（买可乐），在切换前必须要记录当前进程中运行的状态信息，以备下次切换回来的时候可以恢复执行。</strong></p><p>所以，可以发现进程有着「<strong>运行 - 暂停 - 运行</strong>」的活动规律。</p><h3 id="进程的状态" tabindex="-1">进程的状态 <a class="header-anchor" href="#进程的状态" aria-label="Permalink to &quot;进程的状态&quot;">​</a></h3><p>在上面，我们知道了进程有着「运行 - 暂停 - 运行」的活动规律。一般说来，一个进程并不是自始至终连续不停地运行的，它与并发执行中的其他进程的执行是相互制约的。</p><p>它有时处于运行状态，有时又由于某种原因而暂停运行处于等待状态，当使它暂停的原因消失后，它又进入准备运行状态。</p><p>所以，<strong>在一个进程的活动期间至少具备三种基本状态，即运行状态、就绪状态、阻塞状态。</strong></p><img src="https://edu-8673.oss-cn-beijing.aliyuncs.com/img2022.6.30/202207211038547.png" alt="image-20220721103850473" style="zoom:60%;"><p>上图中各个状态的意义：</p><ul><li>运行状态（<em>Runing</em>）：该时刻进程占用 CPU；</li><li>就绪状态（<em>Ready</em>）：可运行，但因为其他进程正在运行而暂停停止；</li><li>阻塞状态（<em>Blocked</em>）：该进程正在等待某一事件发生（如等待输入/输出操作的完成）而暂时停止运行，这时，即使给它CPU控制权，它也无法运行；</li></ul><p>当然，进程另外两个基本状态：</p><ul><li>创建状态（<em>new</em>）：进程正在被创建时的状态；</li><li>结束状态（<em>Exit</em>）：进程正在从系统中消失时的状态；</li></ul><p>于是，一个完整的进程状态的变迁如下图：</p><img src="https://edu-8673.oss-cn-beijing.aliyuncs.com/img2022.6.30/202207211040285.png" alt="image-20220721104012217" style="zoom:80%;"><p>再来详细说明一下进程的状态变迁：</p><ul><li><em>NULL -&gt; 创建状态</em>：一个新进程被创建时的第一个状态；</li><li><em>创建状态 -&gt; 就绪状态</em>：当进程被创建完成并初始化后，一切就绪准备运行时，变为就绪状态，这个过程是很快的；</li><li><em>就绪态 -&gt; 运行状态</em>：处于就绪状态的进程被操作系统的进程调度器选中后，就分配给 CPU 正式运行该进程；</li><li><em>运行状态 -&gt; 结束状态</em>：当进程已经运行完成或出错时，会被操作系统作结束状态处理；</li><li><em>运行状态 -&gt; 就绪状态</em>：处于运行状态的进程在运行过程中，由于分配给它的运行时间片用完，操作系统会把该进程变为就绪态，接着从就绪态选中另外一个进程运行；</li><li><em>运行状态 -&gt; 阻塞状态</em>：当进程请求某个事件且必须等待时，例如请求 I/O 事件；</li><li><em>阻塞状态 -&gt; 就绪状态</em>：当进程要等待的事件完成时，它从阻塞状态变到就绪状态；</li></ul><p>另外，还有一个状态叫<strong>挂起状态</strong>，它表示进程没有占有物理内存空间。这跟阻塞状态是不一样，阻塞状态是等待某个事件的返回。</p><p>由于虚拟内存管理原因，进程的所使用的空间可能并没有映射到物理内存，而是在硬盘上，这时进程就会出现挂起状态，另外调用 sleep 也会被挂起。</p><img src="https://edu-8673.oss-cn-beijing.aliyuncs.com/img2022.6.30/202207211040637.png" alt="image-20220721104029553" style="zoom:67%;"><p>虚拟内存管理-换入换出</p><p>挂起状态可以分为两种：</p><ul><li>阻塞挂起状态：进程在外存（硬盘）并等待某个事件的出现；</li><li>就绪挂起状态：进程在外存（硬盘），但只要进入内存，即刻立刻运行；</li></ul><p>这两种挂起状态加上前面的五种状态，就变成了七种状态变迁（留给我的颜色不多了），见如下图：</p><img src="https://edu-8673.oss-cn-beijing.aliyuncs.com/img2022.6.30/202207211057729.png" alt="image-20220721105730641" style="zoom:50%;"><h3 id="进程的控制结构" tabindex="-1">进程的控制结构 <a class="header-anchor" href="#进程的控制结构" aria-label="Permalink to &quot;进程的控制结构&quot;">​</a></h3><p>在操作系统中，是用<strong>进程控制块</strong>（<em>process control block，PCB</em>）数据结构来描述进程的。</p><p>那 PCB 是什么呢？打开知乎搜索你就会发现这个东西并不是那么简单。</p><img src="https://edu-8673.oss-cn-beijing.aliyuncs.com/img2022.6.30/202207211055815.png" alt="image-20220721105548759" style="zoom:67%;"><p>打住打住，我们是个正经的人，怎么会去看那些问题呢？是吧，回来回来。</p><p><strong>PCB 是进程存在的唯一标识</strong>，这意味着一个进程的存在，必然会有一个 PCB，如果进程消失了，那么 PCB 也会随之消失。</p><blockquote><p>PCB 具体包含什么信息呢？</p></blockquote><h4 id="进程描述信息" tabindex="-1">进程描述信息 <a class="header-anchor" href="#进程描述信息" aria-label="Permalink to &quot;进程描述信息&quot;">​</a></h4><ul><li>进程标识符：标识各个进程，每个进程都有一个并且唯一的标识符；</li><li>用户标识符：进程归属的用户，用户标识符主要为共享和保护服务；</li></ul><h4 id="进程控制和管理信息" tabindex="-1">进程控制和管理信息 <a class="header-anchor" href="#进程控制和管理信息" aria-label="Permalink to &quot;进程控制和管理信息&quot;">​</a></h4><ul><li>进程当前状态，如 new、ready、running、waiting 或 blocked 等；</li><li>进程优先级：进程抢占 CPU 时的优先级；</li></ul><h4 id="资源分配清单" tabindex="-1">资源分配清单 <a class="header-anchor" href="#资源分配清单" aria-label="Permalink to &quot;资源分配清单&quot;">​</a></h4><ul><li>有关内存地址空间或虚拟地址空间的信息，所打开文件的列表和所使用的 I/O 设备信息。</li></ul><h4 id="cpu-相关信息" tabindex="-1">CPU 相关信息 <a class="header-anchor" href="#cpu-相关信息" aria-label="Permalink to &quot;CPU 相关信息&quot;">​</a></h4><ul><li>CPU 中各个寄存器的值，当进程被切换时，CPU 的状态信息都会被保存在相应的 PCB 中，以便进程重新执行时，能从断点处继续执行。</li></ul><p>可见，PCB 包含信息还是比较多的。</p><h3 id="每个-pcb-是如何组织" tabindex="-1">每个 PCB 是如何组织 <a class="header-anchor" href="#每个-pcb-是如何组织" aria-label="Permalink to &quot;每个 PCB 是如何组织&quot;">​</a></h3><p>通常是通过<strong>链表</strong>的方式进行组织，把具有<strong>相同状态的进程链在一起，组成各种队列</strong>。比如：</p><ul><li>将所有处于就绪状态的进程链在一起，称为<strong>就绪队列</strong>；</li><li>把所有因等待某事件而处于等待状态的进程链在一起就组成各种<strong>阻塞队列</strong>；</li><li>另外，对于运行队列在单核 CPU 系统中则只有一个运行指针了，因为单核 CPU 在某个时间，只能运行一个程序。</li></ul><p>那么，就绪队列和阻塞队列链表的组织形式如下图：</p><img src="https://edu-8673.oss-cn-beijing.aliyuncs.com/img2022.6.30/202207211056866.png" alt="image-20220721105611789" style="zoom:67%;"><p>除了链接的组织方式，还有索引方式，它的工作原理：将同一状态的进程组织在一个索引表中，索引表项指向相应的 PCB，不同状态对应不同的索引表。</p><p>一般会选择链表，因为可能面临进程创建，销毁等调度导致进程状态发生变化，所以链表能够更加灵活的插入和删除。</p><h3 id="进程的控制" tabindex="-1">进程的控制 <a class="header-anchor" href="#进程的控制" aria-label="Permalink to &quot;进程的控制&quot;">​</a></h3><p>我们熟知了进程的状态变迁和进程的数据结构 PCB 后，再来看看进程的<strong>创建、终止、阻塞、唤醒</strong>的过程，这些过程也就是进程的控制。</p><h4 id="_01-创建进程" tabindex="-1">01 创建进程 <a class="header-anchor" href="#_01-创建进程" aria-label="Permalink to &quot;01 创建进程&quot;">​</a></h4><p>操作系统允许一个进程创建另一个进程，而且允许子进程继承父进程所拥有的资源，当子进程被终止时，其在父进程处继承的资源应当还给父进程。同时，终止父进程时同时也会终止其所有的子进程。</p><p>创建进程的过程如下：</p><ul><li>为新进程分配一个唯一的进程标识号，并申请一个空白的 PCB，PCB 是有限的，若申请失败则创建失败；</li><li>为进程分配资源，此处如果资源不足，进程就会进入等待状态，以等待资源；</li><li>初始化 PCB；</li><li>如果进程的调度队列能够接纳新进程，那就将进程插入到就绪队列，等待被调度运行；</li></ul><h4 id="_02-终止进程" tabindex="-1"><strong>02 终止进程</strong> <a class="header-anchor" href="#_02-终止进程" aria-label="Permalink to &quot;**02 终止进程**&quot;">​</a></h4><p>进程可以有 3 种终止方式：正常结束、异常结束以及外界干预（信号 <code>kill</code> 掉）。</p><p>终止进程的过程如下：</p><ul><li>查找需要终止的进程的 PCB；</li><li>如果处于执行状态，则立即终止该进程的执行，然后将 CPU 资源分配给其他进程；</li><li>如果其还有子进程，则应将其所有子进程终止；</li><li>将该进程所拥有的全部资源都归还给父进程或操作系统；</li><li>将其从 PCB 所在队列中删除；</li></ul><h4 id="_03-阻塞进程" tabindex="-1"><strong>03 阻塞进程</strong> <a class="header-anchor" href="#_03-阻塞进程" aria-label="Permalink to &quot;**03 阻塞进程**&quot;">​</a></h4><p>当进程需要等待某一事件完成时，它可以调用阻塞语句把自己阻塞等待。而一旦被阻塞等待，它只能由另一个进程唤醒。</p><p>阻塞进程的过程如下：</p><ul><li>找到将要被阻塞进程标识号对应的 PCB；</li><li>如果该进程为运行状态，则保护其现场，将其状态转为阻塞状态，停止运行；</li><li>将该 PCB 插入的阻塞队列中去；</li></ul><p><strong>04 唤醒进程</strong></p><p>进程由「运行」转变为「阻塞」状态是由于进程必须等待某一事件的完成，所以处于阻塞状态的进程是绝对不可能叫醒自己的。</p><p>如果某进程正在等待 I/O 事件，需由别的进程发消息给它，则只有当该进程所期待的事件出现时，才由发现者进程用唤醒语句叫醒它。</p><p>唤醒进程的过程如下：</p><ul><li>在该事件的阻塞队列中找到相应进程的 PCB；</li><li>将其从阻塞队列中移出，并置其状态为就绪状态；</li><li>把该 PCB 插入到就绪队列中，等待调度程序调度；</li></ul><p>进程的阻塞和唤醒是一对功能相反的语句，如果某个进程调用了阻塞语句，则必有一个与之对应的唤醒语句。</p><h3 id="进程的上下文切换" tabindex="-1">进程的上下文切换 <a class="header-anchor" href="#进程的上下文切换" aria-label="Permalink to &quot;进程的上下文切换&quot;">​</a></h3><p>各个进程之间是共享 CPU 资源的，在不同的时候进程之间需要切换，让不同的进程可以在 CPU 执行，那么这个<strong>一个进程切换到另一个进程运行，称为进程的上下文切换</strong>。</p><blockquote><p>在详细说进程上下文切换前，我们先来看看 CPU 上下文切换</p></blockquote><p>大多数操作系统都是多任务，通常支持大于 CPU 数量的任务同时运行。实际上，这些任务并不是同时运行的，只是因为系统在很短的时间内，让各个任务分别在 CPU 运行，于是就造成同时运行的错觉。</p><p>任务是交给 CPU 运行的，那么在每个任务运行前，CPU 需要知道任务从哪里加载，又从哪里开始运行。</p><p>所以，操作系统需要事先帮 CPU 设置好 <strong>CPU 寄存器和程序计数器</strong>。</p><p>CPU 寄存器是 CPU 内部一个容量小，但是速度极快的内存（缓存）。我举个例子，寄存器像是你的口袋，内存像你的书包，硬盘则是你家里的柜子，如果你的东西存放到口袋，那肯定是比你从书包或家里柜子取出来要快的多。</p><p>再来，程序计数器则是用来存储 CPU 正在执行的指令位置、或者即将执行的下一条指令位置。</p><p>所以说，CPU 寄存器和程序计数是 CPU 在运行任何任务前，所必须依赖的环境，这些环境就叫做 <strong>CPU 上下文</strong>。</p><p>既然知道了什么是 CPU 上下文，那理解 CPU 上下文切换就不难了。</p><p>CPU 上下文切换就是先把前一个任务的 CPU 上下文（CPU 寄存器和程序计数器）保存起来，然后加载新任务的上下文到这些寄存器和程序计数器，最后再跳转到程序计数器所指的新位置，运行新任务。</p><p>系统内核会存储保持下来的上下文信息，当此任务再次被分配给 CPU 运行时，CPU 会重新加载这些上下文，这样就能保证任务原来的状态不受影响，让任务看起来还是连续运行。</p><p>上面说到所谓的「任务」，主要包含进程、线程和中断。所以，可以根据任务的不同，把 CPU 上下文切换分成：<strong>进程上下文切换、线程上下文切换和中断上下文切换</strong>。</p><blockquote><p>进程的上下文切换到底是切换什么呢？</p></blockquote><p>进程是由内核管理和调度的，所以进程的切换只能发生在内核态。</p><p>所以，<strong>进程的上下文切换不仅包含了虚拟内存、栈、全局变量等用户空间的资源，还包括了内核堆栈、寄存器等内核空间的资源。</strong></p><p>通常，会把交换的信息保存在进程的 PCB，当要运行另外一个进程的时候，我们需要从这个进程的 PCB 取出上下文，然后恢复到 CPU 中，这使得这个进程可以继续执行，如下图所示：</p><img src="https://edu-8673.oss-cn-beijing.aliyuncs.com/img2022.6.30/202207211054599.png" alt="image-20220721105459539" style="zoom:80%;"><p>大家需要注意，进程的上下文开销是很关键的，我们希望它的开销越小越好，这样可以使得进程可以把更多时间花费在执行程序上，而不是耗费在上下文切换。</p><blockquote><p>发生进程上下文切换有哪些场景？</p></blockquote><ul><li>为了保证所有进程可以得到公平调度，CPU 时间被划分为一段段的时间片，这些时间片再被轮流分配给各个进程。这样，当某个进程的时间片耗尽了，就会被系统挂起，切换到其它正在等待 CPU 的进程运行；</li><li>进程在系统资源不足（比如内存不足）时，要等到资源满足后才可以运行，这个时候进程也会被挂起，并由系统调度其他进程运行；</li><li>当进程通过睡眠函数 sleep 这样的方法将自己主动挂起时，自然也会重新调度；</li><li>当有优先级更高的进程运行时，为了保证高优先级进程的运行，当前进程会被挂起，由高优先级进程来运行；</li><li>发生硬件中断时，CPU 上的进程会被中断挂起，转而执行内核中的中断服务程序；</li></ul><p>以上，就是发生进程上下文切换的常见场景了。</p><h2 id="线程" tabindex="-1">线程 <a class="header-anchor" href="#线程" aria-label="Permalink to &quot;线程&quot;">​</a></h2><p>在早期的操作系统中都是以进程作为独立运行的基本单位，直到后面，计算机科学家们又提出了更小的能独立运行的基本单位，也就是<strong>线程。</strong></p><h3 id="为什么使用线程" tabindex="-1">为什么使用线程？ <a class="header-anchor" href="#为什么使用线程" aria-label="Permalink to &quot;为什么使用线程？&quot;">​</a></h3><p>我们举个例子，假设你要编写一个视频播放器软件，那么该软件功能的核心模块有三个：</p><ul><li>从视频文件当中读取数据；</li><li>对读取的数据进行解压缩；</li><li>把解压缩后的视频数据播放出来；</li></ul><p>对于单进程的实现方式，我想大家都会是以下这个方式：</p><img src="https://edu-8673.oss-cn-beijing.aliyuncs.com/img2022.6.30/202207211049125.png" alt="image-20220721104947064" style="zoom:50%;"><p>单进程实现方式</p><p>对于单进程的这种方式，存在以下问题：</p><ul><li>播放出来的画面和声音会不连贯，因为当 CPU 能力不够强的时候，<code>Read</code> 的时候可能进程就等在这了，这样就会导致等半天才进行数据解压和播放；</li><li>各个函数之间不是并发执行，影响资源的使用效率；</li></ul><p>那改进成多进程的方式：</p><img src="https://edu-8673.oss-cn-beijing.aliyuncs.com/img2022.6.30/202207211050135.png" alt="image-20220721105000074" style="zoom:80%;"><p>多进程实现方式</p><p>对于多进程的这种方式，依然会存在问题：</p><ul><li>进程之间如何通信，共享数据？</li><li>维护进程的系统开销较大，如创建进程时，分配资源、建立 PCB；终止进程时，回收资源、撤销 PCB；进程切换时，保存当前进程的状态信息；</li></ul><p>那到底如何解决呢？需要有一种新的实体，满足以下特性：</p><ul><li>实体之间可以并发运行；</li><li>实体之间共享相同的地址空间；</li></ul><p>这个新的实体，就是<strong>线程( *Thread* )</strong>，线程之间可以并发运行且共享相同的地址空间。</p><h3 id="什么是线程" tabindex="-1">什么是线程？ <a class="header-anchor" href="#什么是线程" aria-label="Permalink to &quot;什么是线程？&quot;">​</a></h3><p><strong>线程是进程当中的一条执行流程。</strong></p><p>同一个进程内多个线程之间可以共享代码段、数据段、打开的文件等资源，但每个线程都有独立一套的寄存器和栈，这样可以确保线程的控制流是相对独立的。</p><img src="https://edu-8673.oss-cn-beijing.aliyuncs.com/img2022.6.30/202207211050825.png" alt="image-20220721105031725" style="zoom:50%;"><h3 id="线程的优缺点" tabindex="-1">线程的优缺点 <a class="header-anchor" href="#线程的优缺点" aria-label="Permalink to &quot;线程的优缺点&quot;">​</a></h3><p>线程的优点：</p><ul><li>一个进程中可以同时存在多个线程；</li><li>各个线程之间可以并发执行；</li><li>各个线程之间可以共享地址空间和文件等资源；</li></ul><p>线程的缺点：</p><ul><li>当进程中的一个线程奔溃时，会导致其所属进程的所有线程奔溃。</li></ul><p>举个例子，对于游戏的用户设计，则不应该使用多线程的方式，否则一个用户挂了，会影响其他同个进程的线程。</p><h3 id="线程与进程的比较" tabindex="-1">线程与进程的比较 <a class="header-anchor" href="#线程与进程的比较" aria-label="Permalink to &quot;线程与进程的比较&quot;">​</a></h3><p>线程与进程的比较如下：</p><ul><li>进程是资源（包括内存、打开的文件等）分配的单位，线程是 CPU 调度的单位；</li><li>进程拥有一个完整的资源平台，而线程只独享必不可少的资源，如寄存器和栈；</li><li>线程同样具有就绪、阻塞、执行三种基本状态，同样具有状态之间的转换关系；</li><li>线程能减少并发执行的时间和空间开销；</li></ul><p>对于，线程相比进程能减少开销，体现在：</p><ul><li>线程的创建时间比进程快，因为进程在创建的过程中，还需要资源管理信息，比如内存管理信息、文件管理信息，而线程在创建的过程中，不会涉及这些资源管理信息，而是共享它们；</li><li>线程的终止时间比进程快，因为线程释放的资源相比进程少很多；</li><li>同一个进程内的线程切换比进程切换快，因为线程具有相同的地址空间（虚拟内存共享），这意味着同一个进程的线程都具有同一个页表，那么在切换的时候不需要切换页表。而对于进程之间的切换，切换的时候要把页表给切换掉，而页表的切换过程开销是比较大的；</li><li>由于同一进程的各线程间共享内存和文件资源，那么在线程之间数据传递的时候，就不需要经过内核了，这就使得线程之间的数据交互效率更高了；</li></ul><p>所以，线程比进程不管是时间效率，还是空间效率都要高。</p><h3 id="线程的上下文切换" tabindex="-1">线程的上下文切换 <a class="header-anchor" href="#线程的上下文切换" aria-label="Permalink to &quot;线程的上下文切换&quot;">​</a></h3><p>在前面我们知道了，线程与进程最大的区别在于：<strong>线程是调度的基本单位，而进程则是资源拥有的基本单位</strong>。</p><p>所以，所谓操作系统的任务调度，实际上的调度对象是线程，而进程只是给线程提供了虚拟内存、全局变量等资源。</p><p>对于线程和进程，我们可以这么理解：</p><ul><li>当进程只有一个线程时，可以认为进程就等于线程；</li><li>当进程拥有多个线程时，这些线程会共享相同的虚拟内存和全局变量等资源，这些资源在上下文切换时是不需要修改的；</li></ul><p>另外，线程也有自己的私有数据，比如栈和寄存器等，这些在上下文切换时也是需要保存的。</p><blockquote><p>线程上下文切换的是什么？</p></blockquote><p>这还得看线程是不是属于同一个进程：</p><ul><li>当两个线程不是属于同一个进程，则切换的过程就跟进程上下文切换一样；</li><li><strong>当两个线程是属于同一个进程，因为虚拟内存是共享的，所以在切换时，虚拟内存这些资源就保持不动，只需要切换线程的私有数据、寄存器等不共享的数据</strong>；</li></ul><p>所以，线程的上下文切换相比进程，开销要小很多。</p><h3 id="线程的实现" tabindex="-1">线程的实现 <a class="header-anchor" href="#线程的实现" aria-label="Permalink to &quot;线程的实现&quot;">​</a></h3><p>主要有三种线程的实现方式：</p><ul><li><strong>用户线程（*User Thread*）</strong>：在用户空间实现的线程，不是由内核管理的线程，是由用户态的线程库来完成线程的管理；</li><li><strong>内核线程（*Kernel Thread*）</strong>：在内核中实现的线程，是由内核管理的线程；</li><li><strong>轻量级进程（*LightWeight Process*）</strong>：在内核中来支持用户线程；</li></ul><p>那么，这还需要考虑一个问题，用户线程和内核线程的对应关系。</p><p>首先，第一种关系是<strong>多对一</strong>的关系，也就是多个用户线程对应同一个内核线程：</p><img src="https://edu-8673.oss-cn-beijing.aliyuncs.com/img2022.6.30/202207211052560.png" alt="image-20220721105202486" style="zoom:50%;"><p>多对一</p><p>第二种是<strong>一对一</strong>的关系，也就是一个用户线程对应一个内核线程：</p><img src="https://edu-8673.oss-cn-beijing.aliyuncs.com/img2022.6.30/202207211052604.png" alt="image-20220721105217523" style="zoom:50%;"><p>一对一</p><p>第三种是<strong>多对多</strong>的关系，也就是多个用户线程对应到多个内核线程：</p><img src="https://edu-8673.oss-cn-beijing.aliyuncs.com/img2022.6.30/202207211052164.png" alt="image-20220721105240071" style="zoom:50%;"><h3 id="用户线程如何理解" tabindex="-1">用户线程如何理解 <a class="header-anchor" href="#用户线程如何理解" aria-label="Permalink to &quot;用户线程如何理解&quot;">​</a></h3><p>用户线程是基于用户态的线程管理库来实现的，那么<strong>线程控制块（*Thread Control Block, TCB*）</strong> 也是在库里面来实现的，对于操作系统而言是看不到这个 TCB 的，它只能看到整个进程的 PCB。</p><p>所以，<strong>用户线程的整个线程管理和调度，操作系统是不直接参与的，而是由用户级线程库函数来完成线程的管理，包括线程的创建、终止、同步和调度等。</strong></p><p>用户级线程的模型，也就类似前面提到的<strong>多对一</strong>的关系，即多个用户线程对应同一个内核线程，如下图所示：</p><img src="https://edu-8673.oss-cn-beijing.aliyuncs.com/img2022.6.30/202207211053605.png" alt="image-20220721105326518" style="zoom:50%;"><p>用户线程的<strong>优点</strong>：</p><ul><li>每个进程都需要有它私有的线程控制块（TCB）列表，用来跟踪记录它各个线程状态信息（PC、栈指针、寄存器），TCB 由用户级线程库函数来维护，可用于不支持线程技术的操作系统；</li><li>用户线程的切换也是由线程库函数来完成的，无需用户态与内核态的切换，所以速度特别快；</li></ul><p>用户线程的<strong>缺点</strong>：</p><ul><li>由于操作系统不参与线程的调度，如果一个线程发起了系统调用而阻塞，那进程所包含的用户线程都不能执行了。</li><li>当一个线程开始运行后，除非它主动地交出 CPU 的使用权，否则它所在的进程当中的其他线程无法运行，因为用户态的线程没法打断当前运行中的线程，它没有这个特权，只有操作系统才有，但是用户线程不是由操作系统管理的。</li><li>由于时间片分配给进程，故与其他进程比，在多线程执行时，每个线程得到的时间片较少，执行会比较慢；</li></ul><p>以上，就是用户线程的优缺点了。</p><h3 id="内核线程如何理解" tabindex="-1">内核线程如何理解 <a class="header-anchor" href="#内核线程如何理解" aria-label="Permalink to &quot;内核线程如何理解&quot;">​</a></h3><p><strong>内核线程是由操作系统管理的，线程对应的 TCB 自然是放在操作系统里的，这样线程的创建、终止和管理都是由操作系统负责。</strong></p><p>内核线程的模型，也就类似前面提到的<strong>一对一</strong>的关系，即一个用户线程对应一个内核线程，如下图所示：</p><img src="https://edu-8673.oss-cn-beijing.aliyuncs.com/img2022.6.30/202207211054456.png" alt="image-20220721105400358" style="zoom:50%;"><p>内核线程的<strong>优点</strong>：</p><ul><li>在一个进程当中，如果某个内核线程发起系统调用而被阻塞，并不会影响其他内核线程的运行；</li><li>分配给线程，多线程的进程获得更多的 CPU 运行时间；</li></ul><p>内核线程的<strong>缺点</strong>：</p><ul><li>在支持内核线程的操作系统中，由内核来维护进程和线程的上下问信息，如 PCB 和 TCB；</li><li>线程的创建、终止和切换都是通过系统调用的方式来进行，因此对于系统来说，系统开销比较大；</li></ul><p>以上，就是内核线的优缺点了。</p><h3 id="轻量级进程如何理解" tabindex="-1">轻量级进程如何理解 <a class="header-anchor" href="#轻量级进程如何理解" aria-label="Permalink to &quot;轻量级进程如何理解&quot;">​</a></h3><p><strong>轻量级进程（*Light-weight process，LWP*）是内核支持的用户线程，一个进程可有一个或多个 LWP，每个 LWP 是跟内核线程一对一映射的，也就是 LWP 都是由一个内核线程支持。</strong></p><p>另外，LWP 只能由内核管理并像普通进程一样被调度，Linux 内核是支持 LWP 的典型例子。</p><p>在大多数系统中，<strong>LWP与普通进程的区别也在于它只有一个最小的执行上下文和调度程序所需的统计信息</strong>。一般来说，一个进程代表程序的一个实例，而 LWP 代表程序的执行线程，因为一个执行线程不像进程那样需要那么多状态信息，所以 LWP 也不带有这样的信息。</p><p>在 LWP 之上也是可以使用用户线程的，那么 LWP 与用户线程的对应关系就有三种：</p><ul><li><code>1 : 1</code>，即一个 LWP 对应 一个用户线程；</li><li><code>N : 1</code>，即一个 LWP 对应多个用户线程；</li><li><code>N : N</code>，即多个 LMP 对应多个用户线程；</li></ul><p>接下来针对上面这三种对应关系说明它们优缺点。先下图的 LWP 模型：</p><img src="https://edu-8673.oss-cn-beijing.aliyuncs.com/img2022.6.30/202207211054759.png" alt="image-20220721105430672" style="zoom:67%;"><h3 id="lwp-模型" tabindex="-1">LWP 模型 <a class="header-anchor" href="#lwp-模型" aria-label="Permalink to &quot;LWP 模型&quot;">​</a></h3><p><strong>1 : 1 模式</strong></p><p>一个线程对应到一个 LWP 再对应到一个内核线程，如上图的进程 4，属于此模型。</p><ul><li>优点：实现并行，当一个 LWP 阻塞，不会影响其他 LWP；</li><li>缺点：每一个用户线程，就产生一个内核线程，创建线程的开销较大。</li></ul><p><strong>N : 1 模式</strong></p><p>多个用户线程对应一个 LWP 再对应一个内核线程，如上图的进程 2，线程管理是在用户空间完成的，此模式中用户的线程对操作系统不可见。</p><ul><li>优点：用户线程要开几个都没问题，且上下文切换发生用户空间，切换的效率较高；</li><li>缺点：一个用户线程如果阻塞了，则整个进程都将会阻塞，另外在多核 CPU 中，是没办法充分利用 CPU 的。</li></ul><p><strong>M : N 模式</strong></p><p>根据前面的两个模型混搭一起，就形成 <code>M:N</code> 模型，该模型提供了两级控制，首先多个用户线程对应到多个 LWP，LWP 再一一对应到内核线程，如上图的进程 3。</p><ul><li>优点：综合了前两种优点，大部分的线程上下文发生在用户空间，且多个线程又可以充分利用多核 CPU 的资源。</li></ul><p><strong>组合模式</strong></p><p>如上图的进程 5，此进程结合 <code>1:1</code> 模型和 <code>M:N</code> 模型。开发人员可以针对不同的应用特点调节内核线程的数目来达到物理并行性和逻辑并行性的最佳方案。</p><h2 id="调度" tabindex="-1">调度 <a class="header-anchor" href="#调度" aria-label="Permalink to &quot;调度&quot;">​</a></h2><p>进程都希望自己能够占用 CPU 进行工作，那么这涉及到前面说过的进程上下文切换。</p><p>一旦操作系统把进程切换到运行状态，也就意味着该进程占用着 CPU 在执行，但是当操作系统把进程切换到其他状态时，那就不能在 CPU 中执行了，于是操作系统会选择下一个要运行的进程。</p><p>选择一个进程运行这一功能是在操作系统中完成的，通常称为<strong>调度程序</strong>（<em>scheduler</em>）。</p><p>那到底什么时候调度进程，或以什么原则来调度进程呢？</p><h3 id="调度时机" tabindex="-1">调度时机 <a class="header-anchor" href="#调度时机" aria-label="Permalink to &quot;调度时机&quot;">​</a></h3><p>在进程的生命周期中，当进程从一个运行状态到另外一状态变化的时候，其实会触发一次调度。</p><p>比如，以下状态的变化都会触发操作系统的调度：</p><ul><li><em>从就绪态 -&gt; 运行态</em>：当进程被创建时，会进入到就绪队列，操作系统会从就绪队列选择一个进程运行；</li><li><em>从运行态 -&gt; 阻塞态</em>：当进程发生 I/O 事件而阻塞时，操作系统必须另外一个进程运行；</li><li><em>从运行态 -&gt; 结束态</em>：当进程退出结束后，操作系统得从就绪队列选择另外一个进程运行；</li></ul><p>因为，这些状态变化的时候，操作系统需要考虑是否要让新的进程给 CPU 运行，或者是否让当前进程从 CPU 上退出来而换另一个进程运行。</p><p>另外，如果硬件时钟提供某个频率的周期性中断，那么可以根据如何处理时钟中断 ，把调度算法分为两类：</p><ul><li><strong>非抢占式调度算法</strong>挑选一个进程，然后让该进程运行直到被阻塞，或者直到该进程退出，才会调用另外一个进程，也就是说不会理时钟中断这个事情。</li><li><strong>抢占式调度算法</strong>挑选一个进程，然后让该进程只运行某段时间，如果在该时段结束时，该进程仍然在运行时，则会把它挂起，接着调度程序从就绪队列挑选另外一个进程。这种抢占式调度处理，需要在时间间隔的末端发生<strong>时钟中断</strong>，以便把 CPU 控制返回给调度程序进行调度，也就是常说的<strong>时间片机制</strong>。</li></ul><h3 id="调度原则" tabindex="-1">调度原则 <a class="header-anchor" href="#调度原则" aria-label="Permalink to &quot;调度原则&quot;">​</a></h3><p><em>原则一</em>：如果运行的程序，发生了 I/O 事件的请求，那 CPU 使用率必然会很低，因为此时进程在阻塞等待硬盘的数据返回。这样的过程，势必会造成 CPU 突然的空闲。所以，<strong>为了提高 CPU 利用率，在这种发送 I/O 事件致使 CPU 空闲的情况下，调度程序需要从就绪队列中选择一个进程来运行。</strong></p><p><em>原则二</em>：有的程序执行某个任务花费的时间会比较长，如果这个程序一直占用着 CPU，会造成系统吞吐量（CPU 在单位时间内完成的进程数量）的降低。所以，<strong>要提高系统的吞吐率，调度程序要权衡长任务和短任务进程的运行完成数量。</strong></p><p><em>原则三</em>：从进程开始到结束的过程中，实际上是包含两个时间，分别是进程运行时间和进程等待时间，这两个时间总和就称为周转时间。进程的周转时间越小越好，<strong>如果进程的等待时间很长而运行时间很短，那周转时间就很长，这不是我们所期望的，调度程序应该避免这种情况发生。</strong></p><p><em>原则四</em>：处于就绪队列的进程，也不能等太久，当然希望这个等待的时间越短越好，这样可以使得进程更快的在 CPU 中执行。所以，<strong>就绪队列中进程的等待时间也是调度程序所需要考虑的原则。</strong></p><p><em>原则五</em>：对于鼠标、键盘这种交互式比较强的应用，我们当然希望它的响应时间越快越好，否则就会影响用户体验了。所以，<strong>对于交互式比较强的应用，响应时间也是调度程序需要考虑的原则。</strong></p><img src="https://edu-8673.oss-cn-beijing.aliyuncs.com/img2022.6.30/202207211041767.png" alt="image-20220721104135697" style="zoom:67%;"><p>针对上面的五种调度原则，总结成如下：</p><ul><li><strong>CPU 利用率</strong>：调度程序应确保 CPU 是始终匆忙的状态，这可提高 CPU 的利用率；</li><li><strong>系统吞吐量</strong>：吞吐量表示的是单位时间内 CPU 完成进程的数量，长作业的进程会占用较长的 CPU 资源，因此会降低吞吐量，相反，短作业的进程会提升系统吞吐量；</li><li><strong>周转时间</strong>：周转时间是进程运行和阻塞时间总和，一个进程的周转时间越小越好；</li><li><strong>等待时间</strong>：这个等待时间不是阻塞状态的时间，而是进程处于就绪队列的时间，等待的时间越长，用户越不满意；</li><li><strong>响应时间</strong>：用户提交请求到系统第一次产生响应所花费的时间，在交互式系统中，响应时间是衡量调度算法好坏的主要标准。</li></ul><p>说白了，这么多调度原则，目的就是要使得进程要「快」。</p><h3 id="调度算法" tabindex="-1">调度算法 <a class="header-anchor" href="#调度算法" aria-label="Permalink to &quot;调度算法&quot;">​</a></h3><p>不同的调度算法适用的场景也是不同的。</p><p>接下来，说说在<strong>单核 CPU 系统</strong>中常见的调度算法。</p><h4 id="_01-先来先服务调度算法" tabindex="-1">01 先来先服务调度算法 <a class="header-anchor" href="#_01-先来先服务调度算法" aria-label="Permalink to &quot;01 先来先服务调度算法&quot;">​</a></h4><p>最简单的一个调度算法，就是非抢占式的<strong>先来先服务（*First Come First Severd, FCFS*）算法</strong>了。</p><img src="https://edu-8673.oss-cn-beijing.aliyuncs.com/img2022.6.30/202207211041537.png" alt="image-20220721104158481" style="zoom:80%;"><p>顾名思义，先来后到，<strong>每次从就绪队列选择最先进入队列的进程，然后一直运行，直到进程退出或被阻塞，才会继续从队列中选择第一个进程接着运行。</strong></p><p>这似乎很公平，但是当一个长作业先运行了，那么后面的短作业等待的时间就会很长，不利于短作业。</p><p>FCFS 对长作业有利，适用于 CPU 繁忙型作业的系统，而不适用于 I/O 繁忙型作业的系统。</p><h4 id="_02-最短作业优先调度算法" tabindex="-1">02 最短作业优先调度算法 <a class="header-anchor" href="#_02-最短作业优先调度算法" aria-label="Permalink to &quot;02 最短作业优先调度算法&quot;">​</a></h4><p><strong>最短作业优先（*Shortest Job First, SJF*）调度算法</strong>同样也是顾名思义，它会<strong>优先选择运行时间最短的进程来运行</strong>，这有助于提高系统的吞吐量。</p><img src="https://edu-8673.oss-cn-beijing.aliyuncs.com/img2022.6.30/202207211042784.png" alt="image-20220721104222727" style="zoom:80%;"><p>这显然对长作业不利，很容易造成一种极端现象。</p><p>比如，一个长作业在就绪队列等待运行，而这个就绪队列有非常多的短作业，那么就会使得长作业不断的往后推，周转时间变长，致使长作业长期不会被运行。</p><h4 id="_03-高响应比优先调度算法" tabindex="-1">03 高响应比优先调度算法 <a class="header-anchor" href="#_03-高响应比优先调度算法" aria-label="Permalink to &quot;03 高响应比优先调度算法&quot;">​</a></h4><p>前面的「先来先服务调度算法」和「最短作业优先调度算法」都没有很好的权衡短作业和长作业。</p><p>那么，<strong>高响应比优先 （*Highest Response Ratio Next, HRRN*）调度算法</strong>主要是权衡了短作业和长作业。</p><p><strong>每次进行进程调度时，先计算「响应比优先级」，然后把「响应比优先级」最高的进程投入运行</strong>，「响应比优先级」的计算公式：</p><img src="https://edu-8673.oss-cn-beijing.aliyuncs.com/img2022.6.30/202207211042239.png" alt="image-20220721104233188" style="zoom:67%;"><p>从上面的公式，可以发现：</p><ul><li>如果两个进程的「等待时间」相同时，「要求的服务时间」越短，「响应比」就越高，这样短作业的进程容易被选中运行；</li><li>如果两个进程「要求的服务时间」相同时，「等待时间」越长，「响应比」就越高，这就兼顾到了长作业进程，因为进程的响应比可以随时间等待的增加而提高，当其等待时间足够长时，其响应比便可以升到很高，从而获得运行的机会；</li></ul><h4 id="_04-时间片轮转调度算法" tabindex="-1">04 时间片轮转调度算法 <a class="header-anchor" href="#_04-时间片轮转调度算法" aria-label="Permalink to &quot;04 时间片轮转调度算法&quot;">​</a></h4><p>最古老、最简单、最公平且使用最广的算法就是<strong>时间片轮转（*Round Robin, RR*）调度算法</strong>。 <img src="https://edu-8673.oss-cn-beijing.aliyuncs.com/img2022.6.30/202207211042664.png" alt="image-20220721104256598" style="zoom:80%;"></p><p><strong>每个进程被分配一个时间段，称为时间片（*Quantum*），即允许该进程在该时间段中运行。</strong></p><ul><li>如果时间片用完，进程还在运行，那么将会把此进程从 CPU 释放出来，并把 CPU 分配另外一个进程；</li><li>如果该进程在时间片结束前阻塞或结束，则 CPU 立即进行切换；</li></ul><p>另外，时间片的长度就是一个很关键的点：</p><ul><li>如果时间片设得太短会导致过多的进程上下文切换，降低了 CPU 效率；</li><li>如果设得太长又可能引起对短作业进程的响应时间变长。将</li></ul><p>通常时间片设为 <code>20ms~50ms</code> 通常是一个比较合理的折中值。</p><h4 id="_05-最高优先级调度算法" tabindex="-1">05 最高优先级调度算法 <a class="header-anchor" href="#_05-最高优先级调度算法" aria-label="Permalink to &quot;05 最高优先级调度算法&quot;">​</a></h4><p>前面的「时间片轮转算法」做了个假设，即让所有的进程同等重要，也不偏袒谁，大家的运行时间都一样。</p><p>但是，对于多用户计算机系统就有不同的看法了，它们希望调度是有优先级的，即希望调度程序能<strong>从就绪队列中选择最高优先级的进程进行运行，这称为最高优先级（*Highest Priority First，HPF*）调度算法</strong>。</p><p>进程的优先级可以分为，静态优先级或动态优先级：</p><ul><li>静态优先级：创建进程时候，就已经确定了优先级了，然后整个运行时间优先级都不会变化；</li><li>动态优先级：根据进程的动态变化调整优先级，比如如果进程运行时间增加，则降低其优先级，如果进程等待时间（就绪队列的等待时间）增加，则升高其优先级，也就是<strong>随着时间的推移增加等待进程的优先级</strong>。</li></ul><p>该算法也有两种处理优先级高的方法，非抢占式和抢占式：</p><ul><li>非抢占式：当就绪队列中出现优先级高的进程，运行完当前进程，再选择优先级高的进程。</li><li>抢占式：当就绪队列中出现优先级高的进程，当前进程挂起，调度优先级高的进程运行。</li></ul><p>但是依然有缺点，可能会导致低优先级的进程永远不会运行。</p><h4 id="_06-多级反馈队列调度算法" tabindex="-1">06 多级反馈队列调度算法 <a class="header-anchor" href="#_06-多级反馈队列调度算法" aria-label="Permalink to &quot;06 多级反馈队列调度算法&quot;">​</a></h4><p><strong>多级反馈队列（*Multilevel Feedback Queue*）调度算法</strong>是「时间片轮转算法」和「最高优先级算法」的综合和发展。</p><p>顾名思义：</p><ul><li>「多级」表示有多个队列，每个队列优先级从高到低，同时优先级越高时间片越短。</li><li>「反馈」表示如果有新的进程加入优先级高的队列时，立刻停止当前正在运行的进程，转而去运行优先级高的队列；</li></ul><img src="https://edu-8673.oss-cn-beijing.aliyuncs.com/img2022.6.30/202207211043740.png" alt="image-20220721104331651" style="zoom:67%;"><p>来看看，它是如何工作的：</p><ul><li>设置了多个队列，赋予每个队列不同的优先级，每个<strong>队列优先级从高到低</strong>，同时<strong>优先级越高时间片越短</strong>；</li><li>新的进程会被放入到第一级队列的末尾，按先来先服务的原则排队等待被调度，如果在第一级队列规定的时间片没运行完成，则将其转入到第二级队列的末尾，以此类推，直至完成；</li><li>当较高优先级的队列为空，才调度较低优先级的队列中的进程运行。如果进程运行时，有新进程进入较高优先级的队列，则停止当前运行的进程并将其移入到原队列末尾，接着让较高优先级的进程运行；</li></ul><p>可以发现，对于短作业可能可以在第一级队列很快被处理完。对于长作业，如果在第一级队列处理不完，可以移入下次队列等待被执行，虽然等待的时间变长了，但是运行时间也会更长了，所以该算法很好的<strong>兼顾了长短作业，同时有较好的响应时间。</strong></p><h3 id="调度算法案例-推荐" tabindex="-1">调度算法案例(推荐) <a class="header-anchor" href="#调度算法案例-推荐" aria-label="Permalink to &quot;调度算法案例(推荐)&quot;">​</a></h3><blockquote><p>看的迷迷糊糊？那我拿去银行办业务的例子，把上面的调度算法串起来，你还不懂，你锤我！</p></blockquote><p><strong>办理业务的客户相当于进程，银行窗口工作人员相当于 CPU。</strong></p><p>现在，假设这个银行只有一个窗口（单核 CPU ），那么工作人员一次只能处理一个业务。</p><img src="https://edu-8673.oss-cn-beijing.aliyuncs.com/img2022.6.30/202207211045154.png" alt="image-20220721104520084" style="zoom:67%;"><p>那么最简单的处理方式，就是先来的先处理，后面来的就乖乖排队，这就是<strong>先来先服务（*FCFS*）调度算法</strong>。但是万一先来的这位老哥是来贷款的，这一谈就好几个小时，一直占用着窗口，这样后面的人只能干等，或许后面的人只是想简单的取个钱，几分钟就能搞定，却因为前面老哥办长业务而要等几个小时，你说气不气人？</p><img src="https://edu-8673.oss-cn-beijing.aliyuncs.com/img2022.6.30/202207211045350.png" alt="image-20220721104542273" style="zoom:80%;"><p>先来先服务</p><p>有客户抱怨了，那我们就要改进，我们干脆优先给那些几分钟就能搞定的人办理业务，这就是<strong>短作业优先（*SJF*）调度算法</strong>。听起来不错，但是依然还是有个极端情况，万一办理短业务的人非常的多，这会导致长业务的人一直得不到服务，万一这个长业务是个大客户，那不就捡了芝麻丢了西瓜</p><img src="https://edu-8673.oss-cn-beijing.aliyuncs.com/img2022.6.30/202207211046237.png" alt="image-20220721104606163" style="zoom:67%;"><p>最短作业优先</p><p>那就公平起见，现在窗口工作人员规定，每个人我只处理 10 分钟。如果 10 分钟之内处理完，就马上换下一个人。如果没处理完，依然换下一个人，但是客户自己得记住办理到哪个步骤了。这个也就是<strong>时间片轮转（*RR*）调度算法</strong>。但是如果时间片设置过短，那么就会造成大量的上下文切换，增大了系统开销。如果时间片过长，相当于退化成退化成 FCFS 算法了。</p><img src="https://edu-8673.oss-cn-beijing.aliyuncs.com/img2022.6.30/202207211046327.png" alt="image-20220721104636248" style="zoom:67%;"><p>时间片轮转</p><p>既然公平也可能存在问题，那银行就对客户分等级，分为普通客户、VIP 客户、SVIP 客户。只要高优先级的客户一来，就第一时间处理这个客户，这就是<strong>最高优先级（*HPF*）调度算法</strong>。但依然也会有极端的问题，万一当天来的全是高级客户，那普通客户不是没有被服务的机会，不把普通客户当人是吗？那我们把优先级改成动态的，如果客户办理业务时间增加，则降低其优先级，如果客户等待时间增加，则升高其优先级。</p><img src="https://edu-8673.oss-cn-beijing.aliyuncs.com/img2022.6.30/202207211048950.png" alt="image-20220721104832872" style="zoom:80%;"><p>最高优先级（静态）</p><p>那有没有兼顾到公平和效率的方式呢？这里介绍一种算法，考虑的还算充分的，<strong>多级反馈队列（*MFQ*）调度算法</strong>，它是时间片轮转算法和优先级算法的综合和发展。它的工作方式：</p><img src="https://edu-8673.oss-cn-beijing.aliyuncs.com/img2022.6.30/202207211048088.png" alt="image-20220721104850011" style="zoom:67%;"><p>多级反馈队列</p><ul><li>银行设置了多个排队（就绪）队列，每个队列都有不同的优先级，<strong>各个队列优先级从高到低</strong>，同时每个队列执行时间片的长度也不同，<strong>优先级越高的时间片越短</strong>。</li><li>新客户（进程）来了，先进入第一级队列的末尾，按先来先服务原则排队等待被叫号（运行）。如果时间片用完客户的业务还没办理完成，则让客户进入到下一级队列的末尾，以此类推，直至客户业务办理完成。</li><li>当第一级队列没人排队时，就会叫号二级队列的客户。如果客户办理业务过程中，有新的客户加入到较高优先级的队列，那么此时办理中的客户需要停止办理，回到原队列的末尾等待再次叫号，因为要把窗口让给刚进入较高优先级队列的客户。</li></ul><p>可以发现，对于要办理短业务的客户来说，可以很快的轮到并解决。对于要办理长业务的客户，一下子解决不了，就可以放到下一个队列，虽然等待的时间稍微变长了，但是轮到自己的办理时间也变长了，也可以接受，不会造成极端的现象，可以说是综合上面几种算法的优点。</p><h1 id="进程间通信" tabindex="-1">进程间通信 <a class="header-anchor" href="#进程间通信" aria-label="Permalink to &quot;进程间通信&quot;">​</a></h1><p><a href="https://mp.weixin.qq.com/s?__biz=Mzg3NzU5NTIwNg==&amp;mid=2247488031&amp;idx=1&amp;sn=645f919135597afcfefe8cba7a573d91&amp;chksm=cf21cd36f85644204baaaf9cd43ae0e23d2aeddd06c5e58ddb50e2f6db79d54ee8088b411402&amp;mpshare=1&amp;scene=23&amp;srcid=0718BxurjYXyytl5hQcxbGd7&amp;sharer_sharetime=1658105912887&amp;sharer_shareid=29b8a04db1dbd975e3bf4e9f47e7ac67#rd" target="_blank" rel="noreferrer">凉了！张三同学没答好「进程间通信」，被面试官挂了.... (qq.com)</a></p><h1 id="操作系统之内存" tabindex="-1">操作系统之内存 <a class="header-anchor" href="#操作系统之内存" aria-label="Permalink to &quot;操作系统之内存&quot;">​</a></h1><h2 id="什么是内存" tabindex="-1">什么是内存 <a class="header-anchor" href="#什么是内存" aria-label="Permalink to &quot;什么是内存&quot;">​</a></h2><h3 id="小故事" tabindex="-1">小故事 <a class="header-anchor" href="#小故事" aria-label="Permalink to &quot;小故事&quot;">​</a></h3><p>我们想去摆地摊（准备运行程序进程）需要经过那几 个步骤，这里猜测一下。</p><p>首先要去城管申请摊位（申请内存），城管（操作系统）根据现在剩余的地毯空间与你地毯的规模划分一块相应大小的摊位（内存）给你，接着你就可以愉快的摆摊（运行程序进程）赚钱啦。</p><p>城管也会时不时的来检查（整理内存空间碎片），摊位是否规整，有没有阻碍正常的人行道。</p><p>简而言之，电脑上的程序（进程）运行是需要使用到对应大小的物理内存。</p><hr><h2 id="虚拟内存" tabindex="-1">虚拟内存 <a class="header-anchor" href="#虚拟内存" aria-label="Permalink to &quot;虚拟内存&quot;">​</a></h2><p>实际上运行的进程并不是直接使用物理内存地址，而是把进程使用的内存地址与实际的物理内存地址做隔离，即操作系统会为每个进程分配独立的一套「<strong>虚拟地址</strong>」。</p><p>每个进程玩自己的地址，互不干涉，至于虚拟地址怎么映射到物理地址，对进程来说是透明的，操作系统已经把这些安排的明明白白了。</p><p>操作系统会提供一种机制，将不同进程的虚拟地址和不同内存的物理地址映射起来，如下图所示</p><img src="https://edu-8673.oss-cn-beijing.aliyuncs.com/img2022.6.30/202207161122938.png" alt="image-20220716112242883" style="zoom:67%;"><p>由此我们引出了两个概念:</p><ul><li>进程中使用的内存地址叫<strong>虚拟地址</strong></li><li>存在计算硬件里的空间地址叫<strong>物理地址</strong></li></ul><p>简单来说操作系统引入虚拟空间，进程持有的虚拟地址会通过 CPU 芯片中的内存管理单元（MMU）的映射关系，来转换成物理地址，再通过物理地址访问物理内存</p><h3 id="操作系统是如何管理虚拟地址与物理内存地址之间关系" tabindex="-1">操作系统是如何管理虚拟地址与物理内存地址之间关系? <a class="header-anchor" href="#操作系统是如何管理虚拟地址与物理内存地址之间关系" aria-label="Permalink to &quot;操作系统是如何管理虚拟地址与物理内存地址之间关系?&quot;">​</a></h3><p>主要有三种方式，分别是<strong>分段、分页、段页</strong>，下面我们来看看这三种内存管理方式</p><hr><h2 id="内存分段" tabindex="-1">内存分段 <a class="header-anchor" href="#内存分段" aria-label="Permalink to &quot;内存分段&quot;">​</a></h2><p>程序包含若干个逻辑分段，如可由代码段、数据段、栈段、堆段组成，每个分段都有不同的属性，所以内存以分段的形式把这些段分离出来进行管理</p><h3 id="在内存分段方式下-虚拟地址和物理地址是如何映射的" tabindex="-1">在内存分段方式下，虚拟地址和物理地址是如何映射的？ <a class="header-anchor" href="#在内存分段方式下-虚拟地址和物理地址是如何映射的" aria-label="Permalink to &quot;在内存分段方式下，虚拟地址和物理地址是如何映射的？&quot;">​</a></h3><p>分段管理下的虚拟地址由两部分组成，段号和段内偏移量</p><img src="https://edu-8673.oss-cn-beijing.aliyuncs.com/img2022.6.30/202207161122937.png" alt="image-20220716112258880" style="zoom:67%;"><ol><li>通过段号映射段表的项</li><li>从项中获取到段基地址</li><li>段基地址+段内偏移量=使用的物理内存</li></ol><p>通过上述知道了，使用段号去映射段表的项，使用项中的段基地址与偏移量计算出物理内存地址，但实际上，分段方式会把程序的虚拟地址分为4段，每个段在段表中有一个项，在这一项找到段的基地址，再加上偏移量计算出物理内存地址</p><p>分段的方式，很好的解决了，程序本身不需要关心具体物理内存地址的问题，但是它仍有不足之处：</p><ul><li>内存碎片的问题</li><li>内存交换的效率低的问题</li></ul><p>接下来对这两个问题进行分析</p><h3 id="分段方式是如何产生内存碎片的" tabindex="-1">分段方式是如何产生内存碎片的？ <a class="header-anchor" href="#分段方式是如何产生内存碎片的" aria-label="Permalink to &quot;分段方式是如何产生内存碎片的？&quot;">​</a></h3><p>在说内存碎片之前，还是先弄明白，什么是内存碎片？，8个人去外面吃饭，因为饭点原因，人比较多，剩下的都是4人小餐桌，这些4人小餐桌就是我们所说的内存碎片，此时会有小伙伴说，把2个4人小餐桌拼凑在一起就解决了这个问题，非常简单，我们把这种方式称为内存碎片整理（涉及到内存交换）。</p><p>回到正题，我们来看一例子，假设物理内存只有1GB （1024MB），用户电脑上运行了多个程序：</p><ul><li>浏览器占用128MB</li><li>音乐软件占用256MB</li><li>游戏占用了512MB</li></ul><p>这个时候我们关闭浏览器，剩余物理内存1024MB -（256MB+512MB）= 256MB。但是这剩余的256MB物理内存不是连续的，被分为了两段128MB，导致没有空间再打开一个200MB的程序，如下图所示</p><img src="https://edu-8673.oss-cn-beijing.aliyuncs.com/img2022.6.30/202207161123917.png" alt="image-20220716112316868" style="zoom:67%;"><p>这里的内存碎片问题共有两点：</p><ul><li>外部内存碎片，就是多个不连续的小物理内存空间，导致新的程序无法被装载</li><li>内部内存碎片，程序所有的内存都被装载进了物理内存，但是程序有部分的内存，可能不经常使用，造成内存的浪费</li></ul><p>解决外部内存碎片的方法就是使用内存碎片整理</p><p>内存碎片整理通过内存交换的方式来实现，我们可以把音乐软件占用的256MB加载到硬盘上面去，再从硬盘读取回来，但是读取回来的位置不再是原来的位置，而是紧跟已经占用的游戏512MB后面，这样两个128MB的空闲物理内存就合并成了一个256MB的连续物理内存，于是新的200MB新程序就能被装载进来</p><p>内存交换空间，在 Linux 系统里，是我们常看到的 Swap 空间，这块空间是从硬盘划分出来的，用于内存与硬盘的空间交换。</p><h3 id="分段方式为什么内存交换效率低" tabindex="-1">分段方式为什么内存交换效率低？ <a class="header-anchor" href="#分段方式为什么内存交换效率低" aria-label="Permalink to &quot;分段方式为什么内存交换效率低？&quot;">​</a></h3><p>首先分段管理容易造成内存碎片，导致内存交换的频率较高，因为硬盘的访问速度比内存慢太多了，然后每次交换的时候，把一大段连续的内存写入到硬盘，再又从硬盘读取出来，如果交换的是一个占内存空间很大的程序，这样整个机器都会显得卡顿，过程也很慢的，所以说分段方式内存交换效率低。</p><p>为了解决内存分段管理造成的内存碎片与内存交换效率低的问题，就出现了内存分页</p><hr><h2 id="内存分页" tabindex="-1"><strong>内存分页</strong> <a class="header-anchor" href="#内存分页" aria-label="Permalink to &quot;**内存分页**&quot;">​</a></h2><p>分段的好处是能产生连续的内存空间，但是会出现大量内存碎片与内存交换效率低的问题</p><p>先思考一下怎么解决这两个问题，内存碎片是由多个不连续的小物理内存空间造成，如果把这些不连续的小物理内存空间组合起来，是不是解决了这个问题？同样的，内存交换的时候我们保证交换的数据小，是不是能提高内存交换的效率？</p><p>这个办法就是内存分页，分页是把整个虚拟与物理空间切成一段段固定尺寸的大小，这样一个连续并且尺寸固定的空间，我们叫页，在 Linux 下，每一页的大小为 4KB。（虚拟空间是指存储一套虚拟地址的空间）</p><p>虚拟地址与物理地址是通过页表来映射，虚拟空间内的虚拟地址一定是连续的，物理地址不一定，但可以通过连续的虚拟地址把多个不连续的物理内存组合使用。</p><img src="https://edu-8673.oss-cn-beijing.aliyuncs.com/img2022.6.30/202207161123519.png" alt="image-20220716112338464" style="zoom:67%;"><p>而当进程访问的虚拟地址在页表中查不到时，系统会产生一个缺页异常，进入系统内核空间分配物理内存、更新进程页表，最后再返回用户空间，恢复进程的运行。</p><h3 id="分页方式是如何解决内存碎片与内存交换效率慢的问题呢" tabindex="-1">分页方式是如何解决内存碎片与内存交换效率慢的问题呢？ <a class="header-anchor" href="#分页方式是如何解决内存碎片与内存交换效率慢的问题呢" aria-label="Permalink to &quot;分页方式是如何解决内存碎片与内存交换效率慢的问题呢？&quot;">​</a></h3><p>内存碎片的解决：</p><p>因为使用内存的单位变成固定大小的页，所以每个程序的虚拟空间维护的也是连续的页(虚拟地址)，通过页表再映射到物理内存页，虽然映射的物理内存页不连续，但是虚拟空间是连续的，可以让它们组合起来使用，但这也只能解决外部内存碎片问题，没有解决内部内碎片问题，因为每页都有固定大小，可能某一页只使用了部分，依然会造成一些浪费。</p><p>内存交换效率慢的解决：</p><p>之前说过，减少交换数据的大小，可以提高内存交换效率，分页方式是这样解决的，如果内存空间不够时，操作系统会把其他正在运行的进程中的「最近没被使用」的内存页释放掉，也就是加载到硬盘，称为换出，一旦需要的时候再加载进来，称为换入。所以一次性写入硬盘的也只有一个页或几个页，内存的交换效率自然就提升了。</p><p>分页方式使加载程序的时候，不再需要一次性都把程序加载到物理内存中。完全可以在进行虚拟内存和物理内存的页之间的映射之后，并不真的把页加载到物理内存里，而是只有在程序运行中，需要用到对应虚拟内存页里面的指令和数据时，再加载到物理内存里面去（用大白话说，当你需要用到的时候才会去使用对应的物理内存）。</p><h3 id="在内存分页方式下-虚拟地址和物理地址是如何映射的" tabindex="-1">在内存分页方式下，虚拟地址和物理地址是如何映射的？ <a class="header-anchor" href="#在内存分页方式下-虚拟地址和物理地址是如何映射的" aria-label="Permalink to &quot;在内存分页方式下，虚拟地址和物理地址是如何映射的？&quot;">​</a></h3><p>在分页机制下，每个进程都会分配一个页表，虚拟地址会分为两部分，页号和页内偏移量，页号作为页表的索引,页表包含物理页每页所在物理内存的基地址，页内偏移量+物理内存基地址就组成了物理内存地址，如下图所示</p><img src="https://edu-8673.oss-cn-beijing.aliyuncs.com/img2022.6.30/202207161123419.png" alt="image-20220716112358362" style="zoom:67%;"><p>就是下面这几步</p><ol><li>页号找到页表中的页项</li><li>获取页项的物理页号基地址</li><li>偏移量+物理页号基地址计算出物理内存地址</li></ol><p>是不是非常的简单，但是这种分页方式使用到操作系统上会不会问题呢？那必然是会有问题的，还记得之前提到的每个进程会分配一个页表嘛？下面来为大家解开这个伏笔</p><h3 id="在分页方式下-每个进程分配一个页表会有什么问题" tabindex="-1">在分页方式下，每个进程分配一个页表会有什么问题？ <a class="header-anchor" href="#在分页方式下-每个进程分配一个页表会有什么问题" aria-label="Permalink to &quot;在分页方式下，每个进程分配一个页表会有什么问题？&quot;">​</a></h3><p>不卖关子了，每个进程分配一个页表会有空间上的缺陷，因为操作系统上可以运行非常多的进程，那不就意味着页表数量非常多！</p><div class="language-apl"><button title="Copy Code" class="copy"></button><span class="lang">apl</span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#F78C6C;">1B</span><span style="color:#89DDFF;">(</span><span style="color:#A6ACCD;">Byte 字节</span><span style="color:#89DDFF;">)=</span><span style="color:#F78C6C;">8bit</span><span style="color:#A6ACCD;">，</span></span>
<span class="line"><span style="color:#F78C6C;">1KB</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">(</span><span style="color:#A6ACCD;">Kilobyte 千字节</span><span style="color:#89DDFF;">)=</span><span style="color:#F78C6C;">1024B</span><span style="color:#A6ACCD;">，</span></span>
<span class="line"><span style="color:#F78C6C;">1MB</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">(</span><span style="color:#A6ACCD;">Megabyte 兆字节 简称“兆”</span><span style="color:#89DDFF;">)=</span><span style="color:#F78C6C;">1024KB</span><span style="color:#A6ACCD;">，</span></span>
<span class="line"><span style="color:#F78C6C;">1GB</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">(</span><span style="color:#A6ACCD;">Gigabyte 吉字节 又称“千兆”</span><span style="color:#89DDFF;">)=</span><span style="color:#F78C6C;">1024MB</span></span></code></pre></div><p>以32 位的环境为例，虚拟地址空间范围共有 4GB，假设一个页的大小是 4KB（2^12），那么就需要大约 100 万 （2^20） 个页，每个「页表项」需要 4 个字节大小来存储，那么整个 4GB 空间范围的映射就要有 4MB 的内存来存储页表。</p><p>4MB看起来不大，但是数量上来了就很恐怖了，假设 100 个进程的话，就需要 400MB 的内存来存储页表，这是非常大的内存了，更别说 64 位的环境了。</p><p>为了解决空间上的问题，在对分页方式的基础上，进行优化，出现了多级页表方式</p><h3 id="多级页表-1" tabindex="-1">多级页表 <a class="header-anchor" href="#多级页表-1" aria-label="Permalink to &quot;多级页表&quot;">​</a></h3><p>在前面我们知道了，分页方式在32位环境下，以每页4KB来计算，一共有100万页，「页表项」需要 4 个字节大小来存储，一个页表包含100万个「页表项」，那么每个进程的页表需要占用4MB大小，多级页表要如何解决这种问题呢？</p><p>在页表的基础上做一次二级分页，把100万「页表项」分为一级页表「1024个页表项」,「一级页表项」下又关联二级页表「1024个页表项」，这样一级页表的1024个页表项就覆盖到了4GB的空间范围映射，并且二级页表按需加载，这样页表占用的空间就大大降低。</p><p>做个简单的计算，假设只有 20% 的一级页表项被用到了，那么页表占用的内存空间就只有 4KB（一级页表） + 20% * 4MB（二级页表）= 0.804MB，这对比单级页表的 4MB 是不是一个巨大的节约？</p><img src="https://edu-8673.oss-cn-beijing.aliyuncs.com/img2022.6.30/202207161125183.png" alt="image-20220716112508116" style="zoom:67%;"><p>接着思考，在二级的基础上是不是又可以继续分级呢，能分二级，必然也能分三级、四级，在64位操作系统是做了四级分页，分为了四个目录，分别是</p><ol><li>全局页目录项</li><li>上层页目录项</li><li>中间页目录项</li><li>页表项</li></ol><img src="https://edu-8673.oss-cn-beijing.aliyuncs.com/img2022.6.30/202207161124188.png" alt="image-20220716112453133" style="zoom:67%;"><h3 id="tbl" tabindex="-1">TBL <a class="header-anchor" href="#tbl" aria-label="Permalink to &quot;TBL&quot;">​</a></h3><p>多级页表虽然解决了空间上的问题，但是我们发现这种方式需要走多道转换才能找到映射的物理内存地址，经过的多道转换造成了时间上的开销。</p><p>程序是局部性的，即在一段时间内，整个程序的执行仅限于程序的某一部分。相应的，执行所访问的存储空间也局限于某个内存区域。</p><p>操作系统就利用这一特性，把最多使用的几个页表项放到TBL缓存, CPU 在寻址时，会先查 TLB，如果没找到，才会继续查常规的页表，TLB 的命中率其实很高的，因为程序最常访问的页就那么几个。</p><h2 id="内存段页" tabindex="-1">内存段页 <a class="header-anchor" href="#内存段页" aria-label="Permalink to &quot;内存段页&quot;">​</a></h2><p>段式与页式并不是相对的，他们也可以组合在一起使用，在段的基础上进行分页分级</p><ol><li>先将程序划分为多个有逻辑意义的段，也就是前面提到的分段机制</li><li>接着再把每个段划分为多个页，也就是对分段划分出来的连续空间，再划分固定大小的页</li></ol><p>虚拟地址结构由段号、段内页号和页内位移三部分组成</p><img src="https://edu-8673.oss-cn-beijing.aliyuncs.com/img2022.6.30/202207161124348.png" alt="image-20220716112440280" style="zoom:67%;"><p>就是下面这几步</p><ol><li>通过段号获取段表的段项</li><li>通过段项获取到页表地址</li><li>通过页表地址找到段页表</li><li>通过段内页号找到段页表的段页项</li><li>通过段页项获取物理页基地址</li><li>通过物理页基地址+偏移量计算出物理内存地址</li></ol><hr><h2 id="总结-1" tabindex="-1">总结 <a class="header-anchor" href="#总结-1" aria-label="Permalink to &quot;总结&quot;">​</a></h2><p>进程并不是直接使用物理内存，而是通过虚拟地址映射使用，所以操作系统会为每个进程分配虚拟空间(一套地址)，使得每个进程使用物理内存互不影响，相互隔离。</p><p>启用大量进程造成内存紧张不足的时候，操作系统会通过内存交换技术，把不常使用的内存加载到硬盘（换出），使用时从硬盘加载到内存（换入）</p><p>操作系统对内存的管理方式分为三种，分段、分页、段页，分段的好处是物理内存空间是连续的，但是缺点很明显，容易造成内存碎片，并且内存交换效率慢，采用分页能很好的解决分段的缺陷，通过连续的虚拟地址解决了外部内存碎片问题，每次内存交换将最近不使用的内存以页的单位换出换入，保证交换数据大小，提高内存交换效率，但是会有页表空间占用问题，为了解决此问题，在分页的基础上优化成多级分页+TBL方式来减少空间占用与时间消耗，最后一个就是段页，段页是分段与分页的结合。</p><p>通过思考，我们发现，多级分页通过树+懒加载+缓存解决了空间占用与时间消耗的问题，虚拟地址很好的做到了让进程与物理内存地址解耦，正因如此，多进程使用物理内存时才不会有冲突，很好的做到了相互独立与隔离。</p><h1 id="操作系统面试题" tabindex="-1">操作系统面试题 <a class="header-anchor" href="#操作系统面试题" aria-label="Permalink to &quot;操作系统面试题&quot;">​</a></h1><p><a href="https://mp.weixin.qq.com/s?__biz=Mzg3NzU5NTIwNg==&amp;mid=2247488080&amp;idx=1&amp;sn=d8af9d51028a4c450e2e5cceac5ac178&amp;chksm=cf21cd79f856446f91da1bf714f81a8a27f3743e7424149f98e560d0339ae0a6133d706dd0b9&amp;mpshare=1&amp;scene=23&amp;srcid=0718tOBk20Pz1LcZlfLdGoTd&amp;sharer_sharetime=1658119045576&amp;sharer_shareid=29b8a04db1dbd975e3bf4e9f47e7ac67#rd" target="_blank" rel="noreferrer">程序员必备的操作系统面试题，建议收藏 (qq.com)</a></p><blockquote><p>面试的过程中，为了考察面试者的基础功力，除了算法以外，操作系统将会占比很大的权重，本文给大家分享我在面试过程中出现的非常高频的面试题，我基本上会从两个角度来阐述，一个是&quot;官话&quot;，一个是“大白话”。希望对即将面试的你有所帮助</p></blockquote><h2 id="_1、为什么有了进程-还要有线程呢" tabindex="-1">1、为什么有了进程，还要有线程呢？ <a class="header-anchor" href="#_1、为什么有了进程-还要有线程呢" aria-label="Permalink to &quot;1、为什么有了进程，还要有线程呢？&quot;">​</a></h2><p>为了提高系统资源的<strong>利用率</strong>和系统的<strong>吞吐量</strong>，通常进程可让多个程序并发的执行，但是也会带来一些问题</p><p><strong>官话</strong></p><ul><li>进程如果在执行的过程被阻塞，那这个进程将被挂起，这时候进程中有些等待的资源得不到执行：</li><li>进程在同一时间只能做一件事儿</li></ul><p>基于以上的<strong>缺点</strong>，操作系统引入了比进程粒度更小的<strong>线程</strong>，作为并发执行的基本单位，从而减少程序在并发执行时所付出的时间和空间开销，提高并发性能。</p><p><code>举个例子</code></p><p>小Q当年开发了一个<strong>聊天软件</strong>，给女朋友说：咋们以后不用什么qq，微信了，我写个聊天工具，咱两正儿八经的两人世界可好。</p><p>说干就干，小Q很快就完成了<strong>开发</strong>，然后开始<strong>测试</strong>，打电话让女朋友让她发个信息，小Q就等着，等啊等啊，怎么还没有发信息过来，没显示啊，一脸懵逼，单步调试吧，发现程序卡在了<strong>wati for user input</strong>不动了。牛逼，程序不输入就没办法执行后面的任务。咋搞？要不<strong>设置个1s</strong>，用户1s不输入直接跳过进行后面的<strong>接收阶段</strong>和<strong>显示阶段</strong>，牛皮牛皮，果然好使，好使个锤锤，这样用户输入信息不就很可能丢失，咋搞？</p><p>能不能将输入和显示这两个动作给<strong>分开</strong>，一个负责输入，发送消息，一个负责读信息和显示。不夸夸你自己吗，直接开干呈现两个窗口。</p><p>回来回来，这就是我们的<strong>多进程</strong>。不过多进程也还是有些问题需要<strong>注意</strong>，开多个窗口没问题，无脑开窗口<strong>撩骚</strong>直接被榨干(内存耗尽)，而且想要几个窗口<strong>交换个数据</strong>也是贼麻烦，这是为啥呢</p><p>多进程的程序，每个进程都有自己的<strong>独立内存空间</strong>，都穿了衣服，不能相互<strong>乱看</strong>，要想通信就要接触<strong>系统层面</strong>来通信，所以肯定就会造成较多的资源消耗和时间浪费。怎么整？</p><p>几个进程为了方便，干脆商量一波，能不能开辟一块内存空间，<strong>共乐</strong>其中，这就是线程非常重要的意义，不过共享了不代表我们就是&quot;<strong>裸</strong>&quot;的，个人保密还是要做到，也不要吵架，可不可以通过锁的方式保密呢，这就涉及到了线程的同步。这样我猜测你应该了解进程和线程了吧。</p><h2 id="_2、简单说下你对并发和并行的理解" tabindex="-1">2、简单说下你对并发和并行的理解？ <a class="header-anchor" href="#_2、简单说下你对并发和并行的理解" aria-label="Permalink to &quot;2、简单说下你对并发和并行的理解？&quot;">​</a></h2><p>官话从概念出发：</p><ul><li>并发</li></ul><blockquote><p>在一个<strong>时间段</strong>中多个程序都启动运行在同一个处理机中</p></blockquote><ul><li>并行</li></ul><blockquote><p>假设目前A，B两个进程，两个进程分别由不同的 CPU 管理执行，两个进程不抢占 CPU 资源且可以<strong>同时运行</strong>，这叫做并行。</p></blockquote><p>例子</p><p>并发是指多个任务在<strong>一段时间</strong>内发生。比如今日成都某家老火锅店做活动，全场5折(这还是比较狠)，但是只有200个位置，但是来的人太多了，来了250个人，此时多出的50个人只好等待着或者去另外家火锅店。那么火锅店老板对这250的安排不是同一时刻安排而是一段时间去处理，其实这就是并发。这个例子好像整的不算生动，我们再来一个</p><p>到了周末就是我们&quot;开黑&quot;的时光，奈何到了周一不迟到怎么对得起自己，但是迟到了被逮住就要被&quot;<strong>BB</strong>&quot;。好嘛，我们作为学生娃儿只好认怂，常规操作，两脚一登，起床，刷牙，上厕所，拿包包<strong>冲</strong>。就是这样类似<strong>复读机</strong>的习惯操作是怎么回事？莫非我们就能同时干这么多事？其实不是的，我们大脑下达指令，起床，刷牙这些操作早形成了<strong>肌肉记忆</strong>，所以我们在这小段时间完成了这么多事儿，还可以多几分钟出来看看美女不香？</p><img src="https://edu-8673.oss-cn-beijing.aliyuncs.com/img2022.6.30/202207201548799.png" alt="image-20220720154841722" style="zoom:50%;"><p>平时玩儿电脑的时候，边写代码边听音乐，计算机同时处理了这么多任务。如果是单核 CPU ，在我们看来这些事儿是同时发生的，其实那是因为底层 CPU 切换的速度太快以致于我们完全感受不到它的切换，仅此为错觉而已。但是如果是多核 CPU ，各个 CPU 负责不同的进程，各个进程不抢占 CPU ，这样同时进行，这就是真正意义上的<strong>并行</strong>。</p><img src="https://edu-8673.oss-cn-beijing.aliyuncs.com/img2022.6.30/202207201548342.png" alt="image-20220720154857276" style="zoom:50%;"><p>说了这么多，那并行和并发到底啥区别？</p><p>两者区别在于是否&quot;<strong>同时</strong>&quot;发生。是在一段时间同时发生还是多个事情在同一个时间点同时发生。</p><h2 id="_3、同步、异步、阻塞、非阻塞的概念" tabindex="-1">3、同步、异步、阻塞、非阻塞的概念 <a class="header-anchor" href="#_3、同步、异步、阻塞、非阻塞的概念" aria-label="Permalink to &quot;3、同步、异步、阻塞、非阻塞的概念&quot;">​</a></h2><blockquote><p>首先大家应该知道同步异步，阻塞非阻塞是<strong>两个不同层面</strong>的问题，一个是operation层面，一个是kernal层面。<strong>同步异步</strong>最大的区别在于是否需要底层的响应再执行。<strong>阻塞非阻塞</strong>最大的区别在于是否立即给出响应。</p></blockquote><p><strong>官话</strong></p><p><strong>同步</strong>：当一个同步调用发出后，<code>调用者要一直等待返回结果</code>。<code>通知后，才能进行后续的执行</code>。</p><p><strong>异步</strong>：当一个异步过程调用发出后，<code>调用者不能立刻得到返回结果</code>。实际处理这个调用的部件在完成后，通过状态、通知和回调来通知调用者。</p><p><strong>阻塞</strong>：<code>是指调用结果返回前，当前线程会被挂起，即阻塞</code>。</p><p><strong>非阻塞</strong>：<code>是指即使调用结果没返回，也不会阻塞当前线程</code>。</p><p><strong>形象比喻</strong>：</p><ul><li>小Q去钓鱼，抛完线后就傻傻的看着有没有动静，有则拉杆(同步阻塞)</li><li>小Q去钓鱼，拿鱼网捞一下，有没有鱼立即知道，不用等，直接就捞(同步非阻塞)</li><li>小Q去钓鱼，这个鱼缸比较牛皮，扔了后自己就打王者荣耀去了，因为鱼上钩了这个鱼缸带的报警器会通知我。这样实现异步(异步非阻塞）</li></ul><h2 id="_4、进程和线程的相关题" tabindex="-1">4、进程和线程的相关题 <a class="header-anchor" href="#_4、进程和线程的相关题" aria-label="Permalink to &quot;4、进程和线程的相关题&quot;">​</a></h2><p><strong>官话</strong>：</p><ul><li>进程：<strong>进程</strong>是系统进行资源分配和调度的一个<strong>独立单位</strong>，是系统中的并发执行的单位。</li><li>线程：<strong>线程</strong>是进程的一个实体，也是 CPU 调度和分派的基本单位，它是比进程更小的能独立运行的基本单位，有时又被称为轻量级进程。</li><li>进程是<strong>资源分配</strong>的最小单位，而线程是 ** CPU 调度**的最小单位；</li><li>创建进程或撤销进程，系统都要为之分配或回收资源，操作系统开销远大于创建或撤销线程时的开销；</li><li>不同进程地址空间相互独立，同一进程内的线程共享同一地址空间。一个进程的线程在另一个进程内是不可见的；</li><li>进程间不会相互影响，而一个线程挂掉将可能导致整个进程挂掉；</li></ul><p><strong>别背了，我知道你们都会背</strong>，举个例子</p><p>计算机中的核心是 CPU ，说它是我们的大脑一点不为过。在此用一个连锁火锅店举例，因为疫情的影响，今天到目前营业额一般，只好开一家店，其他疫情较为严重的店只好先关闭，这里涉及的含义：单个 CPU 一次只运行一个任务。进程就类似这家火锅店，它代表 CPU 所能处理的单个任务，其他地方火锅店只能处于非运行状态。</p><p>一个火锅店有很多服务员，一起协同工作，将火锅店做的红红火火，那么线程就好比这些服务员，一个进程可有多个线程。、</p><p>火锅店各个工作房间是共享的，所有服务员都可以进出，这意味着<strong>进程的内存空间共享</strong>，每个线程都可以使用这些共享内存。但是不是每个房间都可以容纳相同的人数，比如卫生间就只有一个人，其他人不能同时进去。这意味着一个线程在使用共享内存的时候，<strong>其他线程需要等待结束</strong>才能使用这块内存。那怎么防止别人进入呢？很直接的办法就是进去之后记得关门(上锁)，上了锁，其他人想进来发现是上锁了，那就等锁打开后再进去，这就叫做<strong>互斥锁</strong>，防止多个线程同时读写某一块内存区域。</p><p>ok到这里，我们总结一波</p><ul><li>如果以多进程的方式运行，那么允许<strong>多个任务同时</strong>运行</li><li>如果以多线程的方式运行，那么允许将单个任务分成不同的部分运行</li><li>为了防止进程/线程之间产生冲突和允许进程之间的共享资源，需要提供一种协调机制。</li></ul><blockquote><p>多线程与多进程的基本概念</p></blockquote><p>不知道大家经历过食堂打菜的场景没有，如果学校食堂就开设一个窗口，打菜的阿姨也没办法，只好一个个给大家依次打菜，这就好比&quot;单线程&quot;,效率非常低(此处可以考虑为什么redis使用单线程却这么牛逼)</p><p>为了提高效率，在食堂多加了几个窗口，这就类似&quot;多线程&quot;形式。</p><p>那么又想起一个问题，“多线程一定就比单线程效率高麦？”（ps Memcache是多线程模型而Redis是单线程模型)</p><blockquote><p>貌似我们一提到高并发，分布式，就不得不想起多线程，那么多线程一定比单线程效率高？</p></blockquote><p>上面说了采用多线程多核效果更好，但是多线程对 CPU ，内存等都要求比较高，如果存在的上下文切换过于耗时，互斥时间太久，效率反而会低。</p><p>不一定。我不从专业术语来将，举个例子，假设目前接水房有四个水管可以接水，我如果用4个桶分别对应4个水管，那么就比较完美，如果少一个则闲置一个，多一个则会出现抢占。如果此时我的水桶个数大于水管数，为了每个桶都有水，我们就需要切换水桶，这个过程实际上就是<strong>线程的上下文切换</strong>，代价一样不小。</p><blockquote><p>多线程与多进程的应用场景</p></blockquote><p><strong>多线程的优点</strong></p><ul><li>更加高效的内存共享。多进程下内存共享不便</li><li>较轻的上下文切换。因为不用切换地址空间，CR3寄存器和清空TLB</li></ul><p><strong>多进程的优点：</strong></p><ul><li>各个进程有自己内存空间，所以具有更强的容错性，不至于一个集成crash导致系统崩溃</li><li>具有更好的多核可伸缩性，因为进程将地址空间，页表等进行了隔离，在多核的系统上可伸缩性更强</li></ul><p><strong>如何提升多线程的效率</strong></p><ul><li>尽量使用池化技术，也就是线程池，从而不用频繁的创建，销毁线程</li><li>减少线程之间的同步和通信</li><li>通过Huge Page的方式避免产生大量的缺页异常</li><li>避免需要频繁共享写的数据</li></ul><h2 id="_5、进程的状态转换" tabindex="-1">5、进程的状态转换 <a class="header-anchor" href="#_5、进程的状态转换" aria-label="Permalink to &quot;5、进程的状态转换&quot;">​</a></h2><p>在Linux中，进程的状态有七种</p><ul><li><strong>可运行状态</strong></li></ul><blockquote><p>英文名词为TASK_RUNNING，其实这个状态虽然是RUNING，实际上并<strong>不一定</strong>会占有 CPU ，可能修改TASK_RUNABLE会更妥当。TASK_RUNGING根据是否在在 CPU 上运行分为RUNGING和READY两种状态。处于READY状态的进程随时可以运行，只不过因为此时 CPU 资源受限，调度器没选中运行</p></blockquote><img src="https://edu-8673.oss-cn-beijing.aliyuncs.com/img2022.6.30/202207201550674.png" alt="image-20220720155007618" style="zoom:50%;"><ul><li><strong>可中断睡眠状态与不可中断睡眠状态</strong></li></ul><blockquote><p>我们知道进程不可能一直处于可运行的状态。假设A进程需要读取磁盘中的文件，这样的系统调用消耗时间较长，进程需要等待较长的时间才能执行后面的命令，而且等待的时间还是不可估算的，这样的话进程还占用 CPU 就不友好了，因此内核就会将其更改为其他的状态并从 CPU 可运行的队列移除。</p></blockquote><p>Linux中存在两种睡眠状态，分别为：可中断的睡眠状态和不可中断的状态。两者最大的区别为是否响应收到的信号，那么从可中断的睡眠的进程是如何返回到可运行的状态呢</p><ul><li>等待的事情发生且继续运行的条件满足</li><li>收到了没有被屏蔽的信号</li></ul><blockquote><p>处于此状态的进程，收到信号会返回EINTR给用户空间。开发者通过检测返回值的方式进行后续逻辑处理</p></blockquote><p>但是对于不可中断的睡眠状态，就只有一种方式返回到可运行状态，即等待事情发生了继续运行</p><img src="https://edu-8673.oss-cn-beijing.aliyuncs.com/img2022.6.30/202207201550462.png" alt="image-20220720155026412" style="zoom:67%;"><p>上图中为什么出现个 TASK_UNINTERRUPTIBLE 状态，主要是因为内核中的进程不是什么进程都可以被打断，假设响应的是<strong>异步信号</strong>，程序在执行的过程中插入一段用于处理异步信号的而流程，原来的流程就会被中断。所以当进程在和硬件打交道的时候，需要使用 TASK_UNINTERRUPTIBLE 状态将进程保护起来，从而避免进程和设备打交道的过程中被打断导致设备处于不可控的状态。</p><blockquote><p>那么TASK_UNINTERRUPTIBLE状态会出现多久呢？</p></blockquote><p>其实 TASK_UNINTERRUPTIBLE 状态是很危险的状态，因为它刀枪不入，你无法通过信号杀死这样一个不可中断的休眠状态，正常情况，TASK_UNINTERRUPTIBLE状态存在时间很短，但是不排除存在此状态进程比较持久的情况，真的刀枪不入了？可不可以进行提前的预防？</p><p>可以的，早就考虑了。内核提供了<strong>hung task</strong>机制，它会启动一个khungtaskd内核线程对TASK_UNINTERRUPTIBLE状态进行检测，不能让他失控了。khungtaskd会定期的唤醒，如果超过120s都还没有调度，内核就会通过打印警告和堆栈信息。当然，不一定就是120s，可以通过下面选项进行定制</p><div class="language-"><button title="Copy Code" class="copy"></button><span class="lang"></span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#A6ACCD;">sysctl kernel.hung_task_timeout_secs</span></span></code></pre></div><p>说了这么多，我们怎么知道到底有没有出现这个状态，哪里看？可以通过/proc和ps进行查看</p><ul><li><strong>睡眠进程和等待队列</strong></li></ul><p>不管是上面提到的可中断的睡眠进程还是不可中断的睡眠进程，都离不开一种数据结构---<strong>队列</strong>。哦？假设进程A因为某某原因需要休眠，为啥要休眠，等待的资源迟迟拿不到或者等待的事件总是不来，没法进行下一步操作，这个时候内核来了，&quot;行吧，我不会抛弃你，我一定会想办法让你和等待的资源(事件)扯上关系&quot;，只要等待的时机到来我就唤醒你，这采用的方法即&quot;等待队列&quot;。如果进一步深究，想了解它的底层实现(采用了双向链表)，文末我会给大家推荐基本书籍。</p><ul><li>TASK_STOPPED状态于TASK_TRACED状态</li></ul><blockquote><p>TASK_STOPPED状态属于比较特殊的状态，可以通过SIGCONT信号回复进程的执行</p></blockquote><img src="https://edu-8673.oss-cn-beijing.aliyuncs.com/img2022.6.30/202207201551710.png" alt="image-20220720155152660" style="zoom:67%;"><blockquote><p>TASK_TRACED是被跟踪的状态，进程会停下来等待跟踪它的进程对它进行进一步的操作</p></blockquote><ul><li><strong>EXIT_ZOMBIE状态与EXIT_DEAD状态</strong></li></ul><blockquote><p>当进程储于这两种的任意一种，就可以宣布&quot;死亡&quot; 。</p></blockquote><p><img src="" alt="图片">三状态</p><p><strong>就绪 —&gt; 执行</strong>：准备就绪，调度器满足了的需求，给我一种策略，我就可从就绪变为执行的状态；</p><p><strong>执行 —&gt; 阻塞</strong>：不是每个进程都是那么一帆风顺，就像我们每次考试，不管是<strong>中考</strong>，<strong>高考</strong>还是<strong>考研</strong>，难免都会出现磕磕盼盼，遇到了可能暂时会阻挡我们前行的小事儿，可是要相信不会一直的<strong>阻挡</strong>我们，只要我们有恒心坚持，时机到来，你也准备好了，那就<strong>美哉</strong>。回到这里，对于进程而言，当需要等到某个事情发生而无法执行的时候，进程就变为<strong>阻塞</strong>的状态。比如当前进程提出输入请求，如进程提出输入/输出请求，进程所申请资源（主存空间或外部设备）得不到满足时变成<strong>等待资源</strong>状态，进程运行中出现了故障（程序出错或主存储器读写错等）变成等待干预状态等等；</p><p><strong>阻塞 —&gt; 就绪</strong>：处于阻塞状态的进程，在其等待的事件已经发生，如输入/输出完成，资源得到满足或错误处理完毕时，处于等待状态的进程并不马上转入执行状态，而是先转入就绪状态，然后再由系统进程调度程序在适当的时候将该进程转为执行状态；</p><p><strong>执行 —&gt; 就绪</strong>：正在执行的进程，因<strong>时间片</strong>用完而被暂停执行，或在采用<strong>抢先式优先级调度算法</strong>的系统中,当有更高优先级的进程要运行而被迫让出处理机时，该进程便由执行状态转变为就绪状态。</p><h2 id="_6、进程间的通信方式有哪些" tabindex="-1">6、进程间的通信方式有哪些？ <a class="header-anchor" href="#_6、进程间的通信方式有哪些" aria-label="Permalink to &quot;6、进程间的通信方式有哪些？&quot;">​</a></h2><p><strong>管道</strong></p><blockquote><p>学习软件工程规范的时候，我们知道瀑布模型，在整个项目开发过程分为多个阶段，上一阶段的输出作为下一阶段的输入。各个阶段的具体内容如下图所示</p></blockquote><img src="https://edu-8673.oss-cn-beijing.aliyuncs.com/img2022.6.30/202207201552569.png" alt="image-20220720155229505" style="zoom:67%;"><blockquote><p>最初我们在学习Linux基本命令使用的时候，我们经常通过多个命令的组合来完成我们的需求。比如说我们想知道如何查看进程或者端口是否在使用，会使用下面的这条命令</p><p>netstat -nlp | grep XXX</p></blockquote><p>这里的&quot;|&quot;实际上就是管道的意思。&quot;|&quot;前面部分作为&quot;|&quot;后面的输入，很明显是<strong>单向的传输</strong>，这样的管道我们叫做**&quot;匿名管道&quot;**，自行创建和销毁。既然有匿名管道，应该就有带名字的管道&quot;命名管道&quot;。如果你想双向传输，可以考虑使用两个管道拼接即可。</p><p>创建命名管道的方式</p><blockquote><p>mkfifo test</p></blockquote><p>test即为管道的名称，在Linux中一切皆文件，管道也是以文件的方式存在，咋们可以使用ls -l 查看下文件的属性，它会&quot;p&quot;标识。</p><p>下面我们向管道写入内容</p><blockquote><p>echo &quot;666&quot; &gt; test</p></blockquote><p>此时按道理来说咋们已经将内容写入了test，没有直接输出是因为我们需要开启另一个终端进行输出(可以理解为暂存管道)</p><blockquote><p>cat &lt; test</p></blockquote><p>ok，我们发现管道内容被读出来，同时echo退出。那么管道这种通信方式<strong>有什么缺点</strong>？我们知道瀑布模型的软件开发模式是非常低下的，同理采用管道进行通信的效率也很低，因为假设现在有AB两个进程，A进程将数据写入管道，B进程需要等待A进程将信息写完以后才能读出来，所以这种方案不适合频繁的通信。<strong>那优点是什么？</strong></p><p>最明显的优点就是简单，我们平时经常使用以致于都不知道这是管道。鉴于上面的缺点，我们怎么去弥补呢？接着往下看</p><p><strong>消息队列</strong></p><blockquote><p>管道通信属于一股脑的输入，能不能稍微温柔点有规矩点的发送消息？</p></blockquote><p>答：可以的。消息队列在发送数据的时候，按照一个个独立单元(消息体)进行发送，其中每个消息体规定大小块，同时发送方和接收方约定好消息类型或者正文的格式。</p><p>在管道中，其大小受限且只能承载无格式字节流的方式，而消息队列允许不同进程以消息队列的形式发送给任意的进程。</p><p>但是当发送到消息队列的数据太大，需要<strong>拷贝的时间</strong>也就越多，所以还有其他的方式？继续看</p><p><strong>共享内存</strong></p><blockquote><p>使用消息队列可以达到不错的效果，但是如果我们两个部门需要交换比较大的数据的时候，一发一收还是不能及时的感知数据。能不能更好的办法，双方能很快的分享内容数据，答：有的，共享内存</p></blockquote><p>我们知道每个进程都有自己的虚拟内存空间，不同的进程映射到不同的物理内存空间。那么我们可不可以申请一块虚拟地址空间，不同进程通过这块虚拟地址空间映射到相同的屋里地址空间呢？这样不同进程就可以及时的感知进程都干了啥，就不需要再拷贝来拷贝去。</p><p>我们可以通过<strong>shmget</strong>创建一份<strong>共享内存</strong>，并可以通过ipcs命令查看我们创建的共享内存。此时如果一个进程需要访问这段内存，需要将这个内存加载到自己虚拟地址空间的一个位置，让内核给它一个合法地址。使用完毕接触板顶并删除内存对象。</p><p>那么问题来了，这么多进程都共享这块内存，如果同时都往里面写内容，难免会出现冲突的现象，比如A进程写了数字5，B进程同样的地址写了6就直接给覆盖了，这样就不友好了，怎么办？继续往下看</p><p><strong>信号量</strong></p><blockquote><p>为了防止冲突，我们得有个约束或者说一种保护机制。使得同一份共享的资源只能一个进程使用，这里就出现了信号量机制。</p></blockquote><p>信号量实际上是一个计数器，这里需要注意下，信号量主要实现进程之间的同步和互斥，而不是存储通信内容。</p><p>信号量定义了两种操作，p操作和v操作，p操作为申请资源，会将数值减去M，表示这部分被他使用了，其他进程暂时不能用。v操作是归还资源操作，告知归还了资源可以用这部分。</p><p><strong>信号</strong></p><blockquote><p>从管道----消息队列-共享内存/信号量，有需要等待的管道机制，共享内存空间的进程通信方式，还有一种特殊的方式--信号</p></blockquote><p>我们或许听说过运维或者部分开发需要7 * 24小时值守(项目需要上线的时候)，当然也有各种监管，告警系统，一旦出现系统资源紧张等问题就会告知开发或运维人员，对应到操作系统中，这就是<strong>信号</strong>。</p><p>在操作系统中，不同信号用不同的值表示，每个信号设置相应的函数，一旦进程发送某一个信号给另一个进程，另一进程将执行相应的函数进行处理。也就是说把可能出现的异常等问题准备好，一旦信号产生就执行相应的逻辑即可。</p><p><strong>套接字</strong></p><blockquote><p>上面的几种方式都是单机情况下多个进程的通信方式，如果我想和相隔几千里的小姐姐通信怎么办？</p></blockquote><p>这就需要套接字socket了。其实这玩意随处可见，我们平时的聊天，我们天天请求浏览器给予的响应等，都是这老铁。</p><p><strong>小结</strong></p><blockquote><p>分享了一下几种进程间通信方式，希望大家能知其然并知其所以然，机械式的记忆容易忘记哦。</p></blockquote><ul><li>管道</li><li>消息队列</li><li>共享内存</li><li>信号量</li><li>信号</li><li>套接字</li></ul><h2 id="_7、进程的调度算法有哪些" tabindex="-1">7、进程的调度算法有哪些？ <a class="header-anchor" href="#_7、进程的调度算法有哪些" aria-label="Permalink to &quot;7、进程的调度算法有哪些？&quot;">​</a></h2><p>调度算法是指：调度程序是内核的重要组成部分，决定这下一个要运行的进程。那么根据系统的资源分配策略所规定的资源分配算法。常用的调度算法有：先来先服务调度算法、时间片轮转调度法、短作业优先调度算法、最短剩余时间优先、高响应比优先调度算法、优先级调度算法等等。</p><ul><li><strong>先来先服务调度算法</strong></li></ul><p>先来先服务让我们想起了队列的先进先出特性，每一次的调度都从队列中选择最先进入队列的投入运行。</p><p><img src="" alt="图片">先来先服务</p><ul><li><strong>时间片轮转调度算法</strong></li></ul><p>先来理解<strong>轮转</strong>，假设当前进程A、B、C、D，按照进程到达的时间<strong>排序</strong>，而且每个进行都有着同样大小的<strong>时间片</strong>。如果这个进程在当前的时间片运行结束，啥事儿没有，直接将进程从队列<strong>移除</strong>完事儿。如果进程在这个时间片跑完都没有结束，进程变为等待状态，放在进程尾部直到所有进程执行完毕。</p><p>为什么进程要<strong>切换</strong>，切换无外乎是时间片<strong>够用</strong>或者<strong>不够用</strong>。如果时间片够用，那么进程可以运行到结束，结束后删除启动新的时间片。如果时间片不够用，对不起，暂时只能完成一部分任务(变为等待状态)，过后再等待 CPU 的调度。网上开源的代码太多，怎么实现，大家可以参照加深影响。</p><ul><li><strong>短作业优先调度算法</strong></li></ul><p>短作业优先调度算法，从名称可以清晰的知道「<strong>短作业</strong>」意味着执行时间比较短，「<strong>优先</strong>」代表执行顺序。结合就是&quot;短者吃香&quot;。那么多短吃香？进程不可能都短，也有需要执行时间比较长的进程怎么办？一直等待，直到饿死麦？而且有些进程比较<strong>紧急</strong>，能够得到先执行？这些都是此算法所出现的问题，然后出现下面的一些算法</p><ul><li><strong>最短剩余时间优先调度算法</strong></li></ul><p>最短剩余时间是针对最短进程优先增加了<strong>抢占机制</strong>的版本。在这种情况下，进程调度总是选择预期剩余时间最短的进程。当一个进程加入到<strong>就绪队列</strong>时，他可能比当前运行的进程具有更短的剩余时间，因此只要新进程就绪，调度程序就能可能抢占当前正在运行的进程。像最短进程优先一样，调度程序正在执行选择函数是必须有关于处理时间的估计，并且存在长进程饥饿的危险。</p><ul><li><strong>高响应比优先调度算法</strong></li></ul><p>什么是高响应比，有响应之前应该会有请求，相当于是请求+响应+优先，算是一种<strong>综合</strong>的调度算法。也就是它结合了短作业优先，先来先服务以及长作业的一些特性。ok，那么这三种是如何体现出来的</p><p>首先来说<strong>短作业优先</strong>。等待时间我们假设相等，服务时间很短，这样的话短作业就会有更高的优先权。</p><p>再来看<strong>先来先服务</strong>。假设服务时间相同，先来的自然等待时间较长，优先级越高。</p><p>上面说长作业很可能因为等待时间过长，容易饿死。在这里不会，仿佛像医生的这个职业，工作越久资历越老，优先级越来越高，越来越吃香</p><ul><li><strong>优先级调度算法</strong></li></ul><p>优先级调度算法每次从后备作业队列中选择优先级最髙的一个或几个作业，将它们调入内存，分配必要的资源，创建进程并放入就绪队列。在进程调度中，优先级调度算法每次从就绪队列中选择优先级最高的进程，将处理机分配给它，使之投入运行。</p><h2 id="_8、什么是死锁" tabindex="-1">8、什么是死锁？ <a class="header-anchor" href="#_8、什么是死锁" aria-label="Permalink to &quot;8、什么是死锁？&quot;">​</a></h2><p>死锁，顾名思义就是导致线程卡死的锁冲突，例如下面的这种情况</p><img src="https://edu-8673.oss-cn-beijing.aliyuncs.com/img2022.6.30/202207201553516.png" alt="image-20220720155336463" style="zoom:50%;"><p>线程 1 已经成功拿到了<strong>互斥量 1</strong> ，正在申请<strong>互斥量 2</strong> ，而同时在另一个 CPU 上，线程 2 已经拿到了互 <strong>斥量 2</strong> ，正在申请<strong>互斥量 1</strong> 。彼此占有对方正在申请的互斥量，结局就是谁也没办法拿到想要的互斥 量，于是死锁就发生了。</p><img src="https://edu-8673.oss-cn-beijing.aliyuncs.com/img2022.6.30/202207201553983.png" alt="image-20220720155356922" style="zoom:50%;"><p>稍微复杂一点的情况</p><img src="https://edu-8673.oss-cn-beijing.aliyuncs.com/img2022.6.30/202207201554267.png" alt="image-20220720155412208" style="zoom:50%;"><p>存在多个互斥量的情况下，避免死锁最简单的方法就是总是按照一定的先后顺序申请这些互斥 量。还是以刚才的例子为例，如果每个线程都按照先申请互斥量 1 ，再申请互斥量 2 的顺序执行，死锁 就不会发生。有些互斥量有明显的层级关系，但是也有一些互斥量原本就没有特定的层级关系，不过 没有关系，可以人为干预，让所有的线程必须遵循同样的顺序来申请互斥量</p><h2 id="_9、产生死锁的原因" tabindex="-1">9、产生死锁的原因？ <a class="header-anchor" href="#_9、产生死锁的原因" aria-label="Permalink to &quot;9、产生死锁的原因？&quot;">​</a></h2><p>由于系统中存在一些不可剥夺资源，而当两个或两个以上进程占有自身资源，并请求对方资源时，会导致每个进程都无法向前推进，这就是<strong>死锁</strong>。</p><ul><li><strong>竞争资源</strong></li></ul><p>例如：系统中只有一台打印机，可供进程 A 使用，假定 A 已占用了打印机，若 B 继续要求打印机打印将被阻塞。</p><p>系统中的资源可以分为两类：</p><ol><li>可剥夺资源：是指某进程在获得这类资源后，该资源可以再被其他进程或系统剥夺， CPU 和主存均属于可剥夺性资源；</li><li>不可剥夺资源，当系统把这类资源分配给某进程后，再不能强行收回，只能在进程用完后自行释放，如磁带机、打印机等。</li></ol><ul><li>进程推进顺序不当</li></ul><p>例如：进程 A 和 进程 B 互相等待对方的数据。</p><h2 id="_10、死锁产生的必要条件" tabindex="-1">10、死锁产生的必要条件？ <a class="header-anchor" href="#_10、死锁产生的必要条件" aria-label="Permalink to &quot;10、死锁产生的必要条件？&quot;">​</a></h2><p><strong>互斥</strong></p><blockquote><p>要求各个资源互斥，如果这些资源都是可以共享的，那么多个进程直接共享即可，不会存在等待的尴尬场景</p></blockquote><p><strong>非抢占</strong></p><blockquote><p>要求进程所占有的资源使用完后主动释放即可，其他的进程休想抢占这些资源。原因很简单，如果可以抢占，直接拿就好了，不会进入尴尬的等待场景</p></blockquote><p>要求进程是在占有（holding）至少一个资源的前提下，请求（waiting）新的资源的。由于新的资源被其它进程占有，此时，发出请求的进程就会带着自己占有的资源进入阻塞状态。假设 P1，P2 分别都需要 R1，R2 资源，如果是下面这种方式：</p><blockquote><div class="language-"><button title="Copy Code" class="copy"></button><span class="lang"></span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#A6ACCD;">P1:          P2:</span></span>
<span class="line"><span style="color:#A6ACCD;">request(R1)  request(R2)</span></span>
<span class="line"><span style="color:#A6ACCD;">request(R2)  request(R1)</span></span></code></pre></div><p>如果 P1 请求到了 R1 资源之后，P2 请求到了 R2 资源，那么此后不管是哪个进程再次请求资源，都是在占有资源的前提下请求的，此时就会带着这个资源陷入<strong>阻塞状态</strong>。P1 和 P2 需要互相等待，发生了死锁。</p><p>换一种情况：</p><div class="language-"><button title="Copy Code" class="copy"></button><span class="lang"></span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#A6ACCD;">P1:          P2:</span></span>
<span class="line"><span style="color:#A6ACCD;">request(R1)  request(R1)</span></span>
<span class="line"><span style="color:#A6ACCD;">request(R2)  request(R2)</span></span></code></pre></div><p>如果 P1 请求到了 R1 资源，那么 P2 在请求 R1 的时候虽然也会阻塞，但是<strong>是在不占有资源的情况下阻塞的</strong>，不像之前那样占有 R2。所以，此时 P1 可以正常完成任务并释放 R1，P2 拿到 R1 之后再去执行任务。这种情况就不会发生死锁。</p></blockquote><ul><li><strong>循环等待</strong></li></ul><blockquote><p>要求存在一条进程资源的循环等待链，链中的每一个进程占有的资源同时被另一个进程所请求。</p><p>发生死锁时一定有循环等待（因为是死锁的必要条件），但是发生循环等待的时候不一定会发生死锁。这是因为，如果循环等待链中的 P1 和 链外的 P6 都占有某个进程 P2 请求的资源，那么 P2 完全可以选择不等待 P1 释放该资源，而是等待 P6 释放资源。这样就不会发生死锁了。</p></blockquote><h2 id="_11、解决死锁的基本方法" tabindex="-1">11、解决死锁的基本方法？ <a class="header-anchor" href="#_11、解决死锁的基本方法" aria-label="Permalink to &quot;11、解决死锁的基本方法？&quot;">​</a></h2><blockquote><p>如果我们已经知道死锁形成的必要条件，逐一攻破即可。</p></blockquote><ul><li><strong>破坏互斥</strong></li></ul><blockquote><p>通过与锁完全不同的同步方式CAS，CAS提供原子性支持，实现各种无锁的数据结构，不仅可以避免互斥锁带来的开销也可避免死锁问题。</p></blockquote><ul><li>破坏不抢占</li></ul><blockquote><p>如果一个线程已经获取到了一些锁，那么在这个线程释放锁之前这些锁是不会被强制抢占的。但是为了防止死锁的发生，我们可以选择让线程在获取后续的锁失败时主动放弃自己已经持有的锁并在之后重试整个任务，这样其他等待这些锁的线程就可以继续执行了。这样就完美了吗？当然不</p></blockquote><p>这种方式虽然可以在一定程度上避免死锁，但是如果多个相互存在竞争的线程不断的放弃重启放弃循环，就会出现<strong>活锁</strong>的问题，此时线程虽然没有因为锁冲突被卡死，但是仍然会因为阻塞时间太长处于重试当中。怎么办？</p><p>方案1：给任务重试部分增<strong>加随机延迟时间</strong>，降低任务冲突的概率</p><ul><li><strong>破坏环路等待</strong></li></ul><blockquote><p>在实践的过程中，采用破坏环路等待的方式非常常见，这种技术叫做&quot;锁排序&quot;。很好理解，我们假设现在有个数组A，采用单向访问的方式(从前往后)，依次访问并加锁，这样一来，线程只会向前单向等待锁释放，自然也就无法形成一个环路了。</p></blockquote><p>说到这里，我想说死锁不仅仅出现在多线程编程领域，在数据库的访问也是非常的常见，比如我们需要更新数据库的几行数据，就得先获取这些数据的锁，然后通过排序的方式阻止数据层发生死锁。</p><p>这样就完美了？当然没有，那会出现什么问题？</p><p>这种方案也存在它的缺点，比如在大型系统当中，不同模块直接解耦和隔离得非常彻底，不同模块开发人员不清楚其细节，在这样的情况下就很难做到整个系统层面的<strong>全局锁排序</strong>了。在这种情况下，我们可以对方案进行扩充，例如<strong>Linux在内存映射</strong>代码就使用了一种锁分组排序的方式来解决这个问题。锁分组排序首先按模块将锁分为了不同的组，每个组之间定义了严格的加锁顺序，然后再在组内对具体的锁按规则进行排序，这样就保证了全局的加锁顺序一致。在Linux的对应的源码顶部，我们可以看到有非常详尽的注释定义了明确的锁排序规则。</p><p>这种解决方案如果规模过大的话即使可以实现也会非常的脆弱，只要有一个加锁操作没有遵守<strong>锁排序</strong>规则就有可能会引发死锁。不过在像微服务之类解耦比较充分的场景下，只要架构拆分合理，任务模块尽可能小且不会将加锁范围扩大到模块之外，那么锁排序将是一种非常实用和便捷的死锁阻止技术</p><h2 id="_12、怎么预防死锁" tabindex="-1">12、怎么预防死锁？ <a class="header-anchor" href="#_12、怎么预防死锁" aria-label="Permalink to &quot;12、怎么预防死锁？&quot;">​</a></h2><p>破坏请求条件：一次性分配所有资源，这样就不会再有请求了；</p><p>破坏请保持条件：只要有一个资源得不到分配，也不给这个进程分配其他的资源：</p><p>破坏不可剥夺条件：当某进程获得了部分资源，但得不到其它资源，则释放已占有的资源；</p><p>破坏环路等待条件：系统给每类资源赋予一个编号，每一个进程按编号递增的顺序请求资源，释放则相反。</p><h2 id="_13、怎么避免死锁" tabindex="-1">13、怎么避免死锁？ <a class="header-anchor" href="#_13、怎么避免死锁" aria-label="Permalink to &quot;13、怎么避免死锁？&quot;">​</a></h2><ul><li><strong>银行家算法</strong></li></ul><p>当进程首次申请资源时，要测试该进程对资源的<strong>最大需求量</strong>，如果系统现存的资源可以满足它的最大需求量则按当前的申请量分配资源，否则就推迟分配。</p><p>当进程在执行中继续申请资源时，先测试该进程已占用的资源数与本次申请资源数之和是否超过了该进程对资源的最大需求量。若超过则拒绝分配资源。若没超过则再测试系统现存的资源能否满足该进程尚需的最大资源量，若满足则按当前的申请量分配资源，否则也要推迟分配。</p><ul><li><strong>安全序列</strong></li></ul><p>是指系统能按某种进程推进顺序（P1, P2, P3, …, Pn），为每个进程 Pi 分配其所需要的资源，直至满足每个进程对资源的最大需求，使每个进程都可以顺序地完成。这种推进顺序就叫安全序列【银行家算法的核心就是找到一个安全序列】。</p><ul><li><strong>系统安全状态</strong></li></ul><p>如果系统能找到一个安全序列，就称系统处于安全状态，否则，就称系统处于不安全状态。</p><h2 id="_14、怎么解除死锁" tabindex="-1">14、怎么解除死锁？ <a class="header-anchor" href="#_14、怎么解除死锁" aria-label="Permalink to &quot;14、怎么解除死锁？&quot;">​</a></h2><ul><li>资源剥夺：挂起某些死锁进程，并抢占它的资源，将这些资源分配给其他死锁进程（但应该防止被挂起的进程长时间得不到资源）；</li><li>撤销进程：强制撤销部分、甚至全部死锁进程并剥夺这些进程的资源（撤销的原则可以按进程优先级和撤销进程代价的高低进行）；</li><li>进程回退：让一个或多个进程回退到足以避免死锁的地步。进程回退时自愿释放资源而不是被剥夺。要求系统保持进程的历史信息，设置还原点。</li></ul><h2 id="_15、什么是缓冲区溢出-有什么危害" tabindex="-1">15、什么是缓冲区溢出？有什么危害？ <a class="header-anchor" href="#_15、什么是缓冲区溢出-有什么危害" aria-label="Permalink to &quot;15、什么是缓冲区溢出？有什么危害？&quot;">​</a></h2><p>官话</p><blockquote><p>缓冲区溢出是指当计算机向缓冲区内填充数据时超过了缓冲区本身的容量，溢出的数据覆盖在合法数据上</p></blockquote><p>举个例子</p><blockquote><p>一个两升的杯子，你如果想导入三升，怎么做？其他一生只好流出去，不是打湿了电脑就是<strong>那啥</strong>。</p></blockquote><h2 id="_16、物理地址、逻辑地址、线性地址" tabindex="-1">16、物理地址、逻辑地址、线性地址 <a class="header-anchor" href="#_16、物理地址、逻辑地址、线性地址" aria-label="Permalink to &quot;16、物理地址、逻辑地址、线性地址&quot;">​</a></h2><ul><li>物理地址：它是地址转换的最终地址，是内存单元<strong>真正的</strong>地址。如果采用了<strong>分页机制</strong>，那么线性地址会通过<strong>页目录和页表</strong>得方式转换为物理地址。如果没有启用则线性地址即为物理地址</li><li>逻辑地址：在编写c语言的时候，通过&amp;操作符可以读取指针变量本省得值，这个值就是<strong>逻辑地址</strong>。实际上是当前进程得数据段得地址，和真实的物理地址没有关系。只有当在Intel实模式下，逻辑地址==物理地址。我们平时的应用程序都是通过和逻辑地址打交道，至于分页，分段机制对他们而言是透明得。逻辑地址也称作虚拟地址</li><li>线性地址：线性地址是逻辑地址到物理地址的<strong>中间层</strong>。我们编写的代码会存在一个逻辑地址或者是段中得偏移地址，通过相应的计算(加上基地址)生成线性地址。此时如果采用了分页机制，那么吸纳行地址再经过变换即产生物理地址。在Intelk 80386中地址空间容量为4G，各个进程地址空间隔离，意味着每个进程独享4G线性空间。多个进程难免出现进程之间的切换，线性空间随之切换。基于分页机制，对于4GB的线性地址一部分会被映射到物理内存，一部分映射到<strong>磁盘</strong>作为交换文件，一部分没有映射，通过下面加深一下印象</li></ul><h2 id="_17、分页与分段的区别" tabindex="-1">17、分页与分段的区别？ <a class="header-anchor" href="#_17、分页与分段的区别" aria-label="Permalink to &quot;17、分页与分段的区别？&quot;">​</a></h2><p>我们知道计算机的五大组成部分分别为运算器，存储器，存储器 ，输入和输出设备。我们的数据或者指定都需要存放内存然后给 CPU 大哥拿去执行。我们平时写的代码不是直接操作的物理地址，我们所看到的地址实际上叫做虚拟地址，通过相应的转换规则将虚拟地址转换为物理地址。</p><blockquote><p>那么虚拟地址是怎么转换为物理地址的呢？</p></blockquote><p>第一种方式，采用一个映射表代表虚拟地址到物理地址的映射，在计算机中我们叫做<strong>页表</strong>。页表将内存地址分为<strong>页号</strong>和<strong>偏移量</strong>，举个例子</p><p>我们将高位部分称为内存地址的页号，后面的低位叫做<strong>内存地址的偏移量</strong>。我们只需要保存虚拟地址内存的页号和物理内存页号之间的映射关系即可。</p><img src="https://edu-8673.oss-cn-beijing.aliyuncs.com/img2022.6.30/202207201554630.png" alt="image-20220720155457576" style="zoom:50%;"><p>这样说了，也就是三部曲</p><ul><li>虚拟地址-----&gt; 页号+偏移量</li><li>通过页表查询出虚拟页号，对应的物理页号</li><li>物理页号+偏移量-----&gt; 物理内存地址</li></ul><img src="https://edu-8673.oss-cn-beijing.aliyuncs.com/img2022.6.30/202207201555939.png" alt="image-20220720155518877" style="zoom:50%;"><blockquote><p>这样的方法，在32位的内存地址，页表需要多大的空间？</p></blockquote><p>在一个32位的内存地址空间，页表需要记录2^20个物理页面的映射关系，可以想象为要给数组。那么一个页号是完整的4字节。这样一个页表就是4MB。</p><p>再来，我们知道进程有各自的虚拟内存空间，也就是说每个进程都需要一个这样的页表，不管此进程是只有几KB的程序还是需要GB的内存空间都需要这样的页表，用这样的结构保存页面，内存的占用将非常的大，那其他方式是怎么样的呢</p><p>多级页表</p><p>同样的虚拟内存地址，偏移量部分和上面方式一样，但是我们将页号部分拆分为四段，从高到低分成4级到1级的4个页表索引</p><img src="https://edu-8673.oss-cn-beijing.aliyuncs.com/img2022.6.30/202207201555245.png" alt="image-20220720155539171" style="zoom:50%;"><p>这样一来，每个进程将有4级页表。通过4级页表的索引找到对应的条目。通过这个条目找到3级页表所在位置，4级的每一个条目可能有多个3级的条目，找到了3级的条目后找到对应3级索引的条目，就这样到达1级页表。1级对应的则为<strong>物理页号</strong>。最终通过物理页号+偏移量的方式获取物理内存地址。</p><h2 id="_18-为什么使用多级页表" tabindex="-1">18 为什么使用多级页表 <a class="header-anchor" href="#_18-为什么使用多级页表" aria-label="Permalink to &quot;18 为什么使用多级页表&quot;">​</a></h2><ul><li>使用多级页表可以让页表在内存中<strong>离散存储</strong>。多级页表通过索引就可以定位到具体的项。举个例子，假设当前虚拟地址空间为4G，每个页的大小为4k，如果是一级页表的话，共有2……20个页表项，假设每个页表项需要4B，那么存放所有的页表项需要4M，那么为了随机访问，我们就需要连续的4M内存空间存放所有的页表项。这样一来，随着虚拟地址空间的增大，需要存放页表所需的连续空间也就越来多大。如果使用多级页表，我们只需要一页存放目录项，页表存放在内存其他位置即可，下面有例子进一步讲解</li><li>使用多级页表更加<strong>节省页表内存</strong>。理论上，使用一级页表，需要连续存储空间存放所有项。使用多级页表只需要给实际使用的的那些虚拟地址内存的请求分配内存</li></ul><p>举个例子</p><p>假设虚拟地址空间为4G，A进程只是用 4M 的内存空间。对于<strong>一级页表</strong>，我们需要 4M 空间存放这4GB 虚拟地址对应的页表，然后找到进程真正的 4M 内存空间。这样的话，A进程本来只使用 4MB 内存空间，但是为了访问它，我们需要为所有的虚拟地址空间建立页表，岂不是很浪费。对于<strong>二级页表</strong>而言，使用一个页目录就可定位 4M 的内存，存放一个页目录项需要 4k，还需要一页存放进程使用的 4M，4M=1024*4k，也就相当于 <strong>1024</strong> 个页表项就可以映射4M的内存空间，那么总共就只需要4k(页表)+4k(页目录)=8k来存放进程需要的 4M 内存空间对应页表和页目录项。这样看来确实剩下不少的内存。</p><blockquote><p>那使用多级页表有啥缺点？</p></blockquote><p>还是有的，咋们使用一级页表的时候，只需要访问两次内存，一次是访问页表项，一次是访问需要读取的一页数据。如果是二级页表，就需要访问三次，第一次访问页目录，第二次访问页表项，第三次访问读取的数据。访问次数的增加以为访问数据所花费的总时间也增加</p><h2 id="_19、页面置换算法有哪些" tabindex="-1">19、页面置换算法有哪些？ <a class="header-anchor" href="#_19、页面置换算法有哪些" aria-label="Permalink to &quot;19、页面置换算法有哪些？&quot;">​</a></h2><p>请求调页，也称<strong>按需调页</strong>，即对不在内存中的“页”，当进程执行时要用时才调入，否则有可能到程序结束时也不会调入。而内存中给页面留的位置是有限的，在内存中以<strong>帧</strong>为单位放置页面。为了防止请求调页的过程出现过多的内存页面错误（即需要的页面当前不在内存中，需要从硬盘中读数据，也即需要做页面的替换）而使得程序执行效率下降，我们需要设计一些页面置换算法，页面按照这些算法进行相互替换时，可以尽量达到较低的错误率。常用的页面置换算法如下：</p><ul><li><strong>先进先出置换算法（FIFO）</strong></li></ul><p>先进先出，即淘汰最早调入的页面。</p><ul><li><strong>最佳置换算法（OPT）</strong></li></ul><p>选未来最远将使用的页淘汰，是一种最优的方案，可以证明缺页数最小。</p><ul><li><strong>最近最久未使用（LRU）算法</strong></li></ul><p>即选择最近最久未使用的页面予以淘汰</p><ul><li><strong>时钟（Clock）置换算法</strong></li></ul><p>时钟置换算法也叫最近未用算法 NRU（Not RecentlyUsed）。该算法为每个页面设置一位访问位，将内存中的所有页面都通过链接指针链成一个循环队列。。</p><h2 id="_20-书籍-视频学习推荐" tabindex="-1">20 书籍/视频学习推荐 <a class="header-anchor" href="#_20-书籍-视频学习推荐" aria-label="Permalink to &quot;20 书籍/视频学习推荐&quot;">​</a></h2><p>书籍</p><ul><li>Linux内核设计与实现</li><li>操作系统导论</li><li>现代操作系统</li><li>深入理解操作系统</li></ul><p>视频</p><ul><li>B站 ----哈工大李志军老师讲解</li></ul><blockquote><p><a href="https://www.bilibili.com/video/av17036347/" target="_blank" rel="noreferrer">https://www.bilibili.com/video/av17036347/</a></p></blockquote><p><strong>唠嗑</strong></p><p>关于操作系统的内容细节非常的多，也将伴随你的大学四年，考研多半会考，找工作对你底层功力的考察也跑不掉，更重要的在了解其他技术的时候你会发现其实在操作系统基本原理中都有类似思想。</p><p>另外说说工作上的事儿，最近刚工作两周，咋们小组牛批了，八个人六个小姐姐，相关故事后续慢慢道来，一定带劲儿。</p><p>最后，如果你能在这篇文章中有点点收获，请不要吝啬你的<strong>在看</strong>和<strong>点赞</strong>，这将给与小蓝更多写作的动力，fighting。</p><p>周末愉快，每篇进步一点点~~</p><h1 id="文件系统" tabindex="-1">文件系统 <a class="header-anchor" href="#文件系统" aria-label="Permalink to &quot;文件系统&quot;">​</a></h1><p><a href="https://mp.weixin.qq.com/s?__biz=MzUxODAzNDg4NQ==&amp;mid=2247485446&amp;idx=1&amp;sn=2c525f008622b98bc08a66f2b4dcfee8&amp;chksm=f98e4caccef9c5bafe0a69378623049a1cf37fbb8b61b65922f772e2170f98292b914a4268e5&amp;mpshare=1&amp;scene=23&amp;srcid=08080xeA26tX3gMe8FGTAuP0&amp;sharer_sharetime=1659956077614&amp;sharer_shareid=29b8a04db1dbd975e3bf4e9f47e7ac67#rd" target="_blank" rel="noreferrer">一口气搞懂「文件系统」，就靠这 25 张图了 (qq.com)</a></p><h1 id="调度算法-1" tabindex="-1">调度算法 <a class="header-anchor" href="#调度算法-1" aria-label="Permalink to &quot;调度算法&quot;">​</a></h1><p><a href="https://mp.weixin.qq.com/s?__biz=MzUxODAzNDg4NQ==&amp;mid=2247485564&amp;idx=1&amp;sn=b1673a5da4fab943a8a0d27ca1f1fb5c&amp;chksm=f98e4cd6cef9c5c0429a9a3dec153121726f41f6cff36395594fabaad5e5a98ec7b59f29b2c1&amp;mpshare=1&amp;scene=23&amp;srcid=08085G24oFnd5I07Yh5J2SzK&amp;sharer_sharetime=1659956242569&amp;sharer_shareid=29b8a04db1dbd975e3bf4e9f47e7ac67#rd" target="_blank" rel="noreferrer">大厂面试爱问的「调度算法」，20 张图一举拿下 (qq.com)</a></p><h1 id="io技术要点" tabindex="-1">IO技术要点 <a class="header-anchor" href="#io技术要点" aria-label="Permalink to &quot;IO技术要点&quot;">​</a></h1><p><a href="https://mp.weixin.qq.com/s?__biz=Mzg3NzU5NTIwNg==&amp;mid=2247489018&amp;idx=1&amp;sn=994a51dadb83c1df350295d9d6059795&amp;chksm=cf21cad3f85643c50e172ea1eaac7c625e4ba5d2147aa606b4ead9594c3218ccf873346a1eb1&amp;mpshare=1&amp;scene=23&amp;srcid=07180ywjRfM1bcgr0JrhPmuj&amp;sharer_sharetime=1658152060627&amp;sharer_shareid=29b8a04db1dbd975e3bf4e9f47e7ac67#rd" target="_blank" rel="noreferrer">阻塞、非阻塞、多路复用、同步、异步、BIO、NIO、AIO 一锅端 (qq.com)</a></p><p>阻塞、非阻塞、多路复用、同步、异步、BIO、NIO、AIO 一锅端</p><p>关于IO会涉及到阻塞、非阻塞、多路复用、同步、异步、BIO、NIO、AIO等几个知识点。知识点虽然不难但平常经常容易搞混，特此Mark下，与君共勉。</p><h2 id="_1-阻塞跟非阻塞" tabindex="-1">1 阻塞跟非阻塞 <a class="header-anchor" href="#_1-阻塞跟非阻塞" aria-label="Permalink to &quot;1 阻塞跟非阻塞&quot;">​</a></h2><h3 id="_1-阻塞" tabindex="-1">1 阻塞 <a class="header-anchor" href="#_1-阻塞" aria-label="Permalink to &quot;1 阻塞&quot;">​</a></h3><img src="https://edu-8673.oss-cn-beijing.aliyuncs.com/img2022.6.30/202207201648429.png" alt="image-20220720164804352" style="zoom:50%;"> 阻塞IO情况下，当用户调用`read`后，用户线程会被阻塞，等内核数据准备好并且数据从内核缓冲区拷贝到用户态缓存区后`read`才会返回。可以看到是阻塞的两个部分。 <ol><li>CPU把数据从磁盘读到内核缓冲区。</li><li>CPU把数据从内核缓冲区拷贝到用户缓冲区。</li></ol><h3 id="_2-非阻塞" tabindex="-1">2 非阻塞 <a class="header-anchor" href="#_2-非阻塞" aria-label="Permalink to &quot;2 非阻塞&quot;">​</a></h3><p><img src="https://mmbiz.qpic.cn/mmbiz_png/wJvXicD0z2dVkSedadpZXOO60n7ib3Qv0RicgbO9MFacfCAQ25qbYymBU4OCkfT6TX4srR8ph20g8BBU3jXQY80jQ/640?wx_fmt=png&amp;wxfrom=5&amp;wx_lazy=1&amp;wx_co=1" alt="图片">非阻塞IO</p><p>非阻塞IO发出read请求后发现数据没准备好，会继续往下执行，此时应用程序会不断轮询polling内核询问数据是否准备好，当数据没有准备好时，内核立即返回EWOULDBLOCK错误。直到数据被拷贝到应用程序缓冲区，read请求才获取到结果。并且你要注意！这里最后一次 read 调用获取数据的过程，是一个同步的过程，是需要等待的过程。这里的同步指的是<strong>内核态的数据拷贝到用户程序的缓存区这个过程</strong>。</p><h3 id="_3-io多路复用" tabindex="-1">3 IO多路复用 <a class="header-anchor" href="#_3-io多路复用" aria-label="Permalink to &quot;3 IO多路复用&quot;">​</a></h3><img src="https://edu-8673.oss-cn-beijing.aliyuncs.com/img2022.6.30/202207201650661.png" alt="image-20220720165016581" style="zoom:50%;"> 非阻塞情况下无可用数据时，应用程序每次轮询内核看数据是否准备好了也耗费CPU，能否不让它轮询，当内核缓冲区数据准备好了，以事件通知当机制告知应用进程数据准备好了呢？应用进程在没有收到数据准备好的事件通知信号时可以忙写其他的工作。此时**IO多路复用**就派上用场了。 <p>IO多路复用中文比较让人头大，IO多路复用的原文叫 I/O multiplexing，这里的 multiplexing 指的其实是在单个线程通过记录跟踪每一个Sock(I/O流)的状态来同时管理多个I/O流. 发明它的目的是尽量多的提高服务器的吞吐能力。实现一个线程监控多个IO请求，哪个IO有请求就把数据从内核拷贝到进程缓冲区，拷贝期间是阻塞的！现在已经可以通过采用mmap地址映射的方法，达到内存共享效果，避免真复制，提高效率。</p><img src="https://edu-8673.oss-cn-beijing.aliyuncs.com/img2022.6.30/202207201650836.png" alt="image-20220720165033786" style="zoom:80%;"><p>像<strong>select、poll、epoll</strong> 都是I/O多路复用的具体的实现。</p><h5 id="_1-select" tabindex="-1">1 select <a class="header-anchor" href="#_1-select" aria-label="Permalink to &quot;1 select&quot;">​</a></h5><p>select是第一版IO复用，提出后暴漏了很多问题。</p><ol><li>select 会修改传入的参数数组，这个对于一个需要调用很多次的函数，是非常不友好的。</li><li>select 如果任何一个sock(I/O stream)出现了数据，select 仅仅会返回，但不会告诉是那个sock上有数据，只能自己遍历查找。</li><li>select 只能监视1024个链接。</li><li>select 不是线程安全的，如果你把一个sock加入到select, 然后突然另外一个线程发现这个sock不用，要收回，这个select 不支持的。</li></ol><h5 id="_2-poll" tabindex="-1">2 <strong>poll</strong> <a class="header-anchor" href="#_2-poll" aria-label="Permalink to &quot;2 **poll**&quot;">​</a></h5><p>poll 修复了 select 的很多问题。</p><ol><li>poll 去掉了1024个链接的限制。</li><li>poll 从设计上来说不再修改传入数组。</li></ol><p>但是poll仍然不是线程安全的， 这就意味着不管服务器有多强悍，你也只能在一个线程里面处理一组 I/O 流。你当然可以拿多进程来配合了，不过然后你就有了多进程的各种问题。</p><h5 id="_3-epoll" tabindex="-1">3 epoll <a class="header-anchor" href="#_3-epoll" aria-label="Permalink to &quot;3 epoll&quot;">​</a></h5><p>epoll 可以说是 I/O 多路复用最新的一个实现，epoll 修复了poll 和select绝大部分问题， 比如：</p><ol><li>epoll 现在是线程安全的。</li><li>epoll 现在不仅告诉你sock组里面数据，还会告诉你具体哪个sock有数据，你不用自己去找了。</li><li>epoll 内核态管理了各种IO文件描述符， 以前用户态发送所有文件描述符到内核态，然后内核态负责筛选返回可用数组，现在epoll模式下所有文件描述符在内核态有存，查询时不用传文件描述符进去了。</li></ol><h5 id="_4-三者对比" tabindex="-1">4 三者对比 <a class="header-anchor" href="#_4-三者对比" aria-label="Permalink to &quot;4 三者对比&quot;">​</a></h5><img src="https://edu-8673.oss-cn-beijing.aliyuncs.com/img2022.6.30/202207201649016.png" alt="image-20220720164939930" style="zoom:50%;"> 横轴 Dead connections 是链接数的意思，叫这个名字只是它的测试工具叫deadcon。纵轴是每秒处理请求的数量，可看到epoll每秒处理请求的数量基本不会随着链接变多而下降的。poll 和/dev/poll 就很惨了。但 epoll 有个致命的缺点是只有**linux**支持。 <p>比如平常Nginx为何可以支持4W的QPS是因为它会使用目标平台上面最高效的I/O多路复用模型。</p><h3 id="_4-异步io" tabindex="-1">4 异步IO <a class="header-anchor" href="#_4-异步io" aria-label="Permalink to &quot;4 异步IO&quot;">​</a></h3><img src="https://edu-8673.oss-cn-beijing.aliyuncs.com/img2022.6.30/202207201649576.png" alt="image-20220720164910509" style="zoom:50%;"> 然后你会发现上面的提到过的操作都不是真正的异步，因为两个阶段总要等待会儿！而真正的异步 I/O 是内核数据准备好和数据从内核态拷贝到用户态这两个过程都不用等待。 <p>很庆幸，Linux给我们准备了<strong>aio_read</strong>跟<strong>aio_write</strong>函数实现真实的异步，当用户发起aio_read请求后就会自动返回。内核会自动将数据从内核缓冲区拷贝到用户进程空间，应用进程啥都不用管。</p><h2 id="_2-同步跟异步" tabindex="-1">2 同步跟异步 <a class="header-anchor" href="#_2-同步跟异步" aria-label="Permalink to &quot;2 同步跟异步&quot;">​</a></h2><h3 id="_1-同步" tabindex="-1">1 同步 <a class="header-anchor" href="#_1-同步" aria-label="Permalink to &quot;1 同步&quot;">​</a></h3><p>同步跟异步的区别在于<code>数据从内核空间拷贝到用户空间是否由用户线程完成</code>，这里又分为同步阻塞跟同步非阻塞两种。</p><ol><li>同步阻塞：此时一个线程维护一个连接，该线程完成数据到读写跟处理到全部过程，数据读写时时线程是被阻塞的。</li><li>同步非阻塞：非阻塞的意思是用户线程发出读请求后，读请求不会阻塞当前用户线程，不过用户线程还是要不断的去主动判断数据是否准备OK了。此时还是会阻塞等待内核复制数据到用户进程。他与同步BIO区别是使用一个连接全程等待</li></ol><p>我们以同步非阻塞为例，如下可看到，在将数据从内核拷贝到用户空间这一过程，是由用户线程阻塞完成的。</p><img src="https://edu-8673.oss-cn-beijing.aliyuncs.com/img2022.6.30/202207201651813.png" alt="image-20220720165123728" style="zoom:50%;"><h3 id="_2-异步" tabindex="-1">2 异步 <a class="header-anchor" href="#_2-异步" aria-label="Permalink to &quot;2 异步&quot;">​</a></h3><p>对于异步来说，用户进行读或者写后，将立刻返回，由内核去完成数据读取以及拷贝工作，完成后通知用户，并执行回调函数（用户提供的callback），此时数据已从内核拷贝到用户空间，用户线程只需要对数据进行处理即可，不需要关注读写，用户不需要等待内核对数据的复制操作，用户在得到通知时数据已经被复制到用户空间。我们以如下的真实异步非阻塞为例。</p><img src="https://edu-8673.oss-cn-beijing.aliyuncs.com/img2022.6.30/202207201651743.png" alt="image-20220720165140668" style="zoom:50%;"> 可发现，用户在调用之后会立即返回，由内核完成数据的拷贝工作，并通知用户线程，进行回调。 <h3 id="_3-同步跟异步对比" tabindex="-1">3 同步跟异步对比 <a class="header-anchor" href="#_3-同步跟异步对比" aria-label="Permalink to &quot;3 同步跟异步对比&quot;">​</a></h3><p>同步关注的消息通信机制synchronous communication，在发出一个调用时，在没有得到结果之前，该调用就不返回。但是一旦调用返回，就得到返回值了。换句话说，就是由调用者主动等待这个调用的结果。</p><p>异步关注消息通信机制asynchronous communication，调用在发出之后，这个调用就直接返回了，所以没有返回结果。换句话说，当一个异步过程调用发出后，调用者不会立刻得到结果。而是在调用发出后，被调用者通过状态、通知来通知调用者，或通过回调函数处理这个调用。</p><h2 id="_3-java-io" tabindex="-1">3 Java IO <a class="header-anchor" href="#_3-java-io" aria-label="Permalink to &quot;3 Java IO&quot;">​</a></h2><p>在Java中，我们使用socket进行网络通信，IO主要有三种模式，主要看<strong>内核支持</strong>哪些。</p><ol><li><code>BIO</code>：同步阻塞IO。</li><li><code>NIO</code>：同步非阻塞IO。</li><li><code>AIO</code>：异步非阻塞IO。</li></ol><h3 id="_1-bio" tabindex="-1">1 BIO <a class="header-anchor" href="#_1-bio" aria-label="Permalink to &quot;1 BIO&quot;">​</a></h3><p><strong>同步阻塞IO</strong>，每个客户端的Socket连接请求，服务端都会对应有个处理线程与之对应，对于没有分配到处理线程的连接就会被阻塞或者拒绝。相当于是<code>一个连接一个线程</code>。</p><img src="https://edu-8673.oss-cn-beijing.aliyuncs.com/img2022.6.30/202207201652650.png" alt="image-20220720165215586" style="zoom:67%;"> **BIO特点**： <ol><li>使用一个独立的线程维护一个socket连接，随着连接数量的增多，对虚拟机造成一定压力。</li><li>使用流来读取数据，流是阻塞的，当没有可读／可写数据时，线程等待，会造成资源的浪费。</li></ol><p>1 BIO 样例</p><p>常量：</p><div class="language-java"><button title="Copy Code" class="copy"></button><span class="lang">java</span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#C792EA;">public</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">class</span><span style="color:#A6ACCD;"> </span><span style="color:#FFCB6B;">Constant</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">{</span></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#C792EA;">public</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">static</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">final</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">String</span><span style="color:#A6ACCD;"> HOST </span><span style="color:#89DDFF;">=</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">&quot;</span><span style="color:#C3E88D;">127.0.0.1</span><span style="color:#89DDFF;">&quot;</span><span style="color:#89DDFF;">;</span></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#C792EA;">public</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">static</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">final</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">int</span><span style="color:#A6ACCD;"> PORT </span><span style="color:#89DDFF;">=</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">8080</span><span style="color:#89DDFF;">;</span></span>
<span class="line"><span style="color:#89DDFF;">}</span></span></code></pre></div><p>主类：</p><div class="language-java"><button title="Copy Code" class="copy"></button><span class="lang">java</span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#C792EA;">public</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">class</span><span style="color:#A6ACCD;"> </span><span style="color:#FFCB6B;">ClientMain</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">{</span></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#C792EA;">public</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">static</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">void</span><span style="color:#A6ACCD;"> </span><span style="color:#82AAFF;">main</span><span style="color:#89DDFF;">(</span><span style="color:#C792EA;">String</span><span style="color:#89DDFF;">[]</span><span style="color:#A6ACCD;"> </span><span style="color:#A6ACCD;font-style:italic;">args</span><span style="color:#89DDFF;">)</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">{</span></span>
<span class="line"><span style="color:#89DDFF;">        </span><span style="color:#676E95;font-style:italic;">//开启服务</span></span>
<span class="line"><span style="color:#A6ACCD;">        System</span><span style="color:#89DDFF;">.</span><span style="color:#A6ACCD;">out</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">println</span><span style="color:#89DDFF;">(</span><span style="color:#89DDFF;">&quot;</span><span style="color:#C3E88D;">开启服务,监听端口:</span><span style="color:#89DDFF;">&quot;</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">+</span><span style="color:#A6ACCD;"> Constant</span><span style="color:#89DDFF;">.</span><span style="color:#A6ACCD;">PORT</span><span style="color:#89DDFF;">);</span></span>
<span class="line"><span style="color:#A6ACCD;">        </span><span style="color:#89DDFF;font-style:italic;">new</span><span style="color:#A6ACCD;"> </span><span style="color:#82AAFF;">Thread</span><span style="color:#89DDFF;">(</span><span style="color:#89DDFF;font-style:italic;">new</span><span style="color:#A6ACCD;"> </span><span style="color:#82AAFF;">ServerThread</span><span style="color:#89DDFF;">()).</span><span style="color:#82AAFF;">start</span><span style="color:#89DDFF;">();</span></span>
<span class="line"><span style="color:#89DDFF;">        </span><span style="color:#676E95;font-style:italic;">//建立一个socket客户端,发起请求</span></span>
<span class="line"><span style="color:#A6ACCD;">        System</span><span style="color:#89DDFF;">.</span><span style="color:#A6ACCD;">out</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">println</span><span style="color:#89DDFF;">(</span><span style="color:#89DDFF;">&quot;</span><span style="color:#C3E88D;">客户端,请求连接,并发送数据</span><span style="color:#89DDFF;">&quot;</span><span style="color:#89DDFF;">);</span></span>
<span class="line"><span style="color:#A6ACCD;">        </span><span style="color:#89DDFF;font-style:italic;">try</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">{</span></span>
<span class="line"><span style="color:#A6ACCD;">            </span><span style="color:#C792EA;">Socket</span><span style="color:#A6ACCD;"> socket </span><span style="color:#89DDFF;">=</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;font-style:italic;">new</span><span style="color:#A6ACCD;"> </span><span style="color:#82AAFF;">Socket</span><span style="color:#89DDFF;">(</span><span style="color:#A6ACCD;">Constant</span><span style="color:#89DDFF;">.</span><span style="color:#A6ACCD;">HOST</span><span style="color:#89DDFF;">,</span><span style="color:#A6ACCD;">Constant</span><span style="color:#89DDFF;">.</span><span style="color:#A6ACCD;">PORT</span><span style="color:#89DDFF;">);</span></span>
<span class="line"><span style="color:#89DDFF;">            </span><span style="color:#676E95;font-style:italic;">//开启新的线程处理socket连接</span></span>
<span class="line"><span style="color:#A6ACCD;">            </span><span style="color:#89DDFF;font-style:italic;">new</span><span style="color:#A6ACCD;"> </span><span style="color:#82AAFF;">Thread</span><span style="color:#89DDFF;">(</span><span style="color:#89DDFF;font-style:italic;">new</span><span style="color:#A6ACCD;"> </span><span style="color:#82AAFF;">ClientProcessThread</span><span style="color:#89DDFF;">(</span><span style="color:#A6ACCD;">socket</span><span style="color:#89DDFF;">)).</span><span style="color:#82AAFF;">start</span><span style="color:#89DDFF;">();</span></span>
<span class="line"><span style="color:#A6ACCD;">        </span><span style="color:#89DDFF;">}</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;font-style:italic;">catch</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">(</span><span style="color:#C792EA;">IOException</span><span style="color:#A6ACCD;"> </span><span style="color:#A6ACCD;font-style:italic;">e</span><span style="color:#89DDFF;">)</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">{</span></span>
<span class="line"><span style="color:#A6ACCD;">            e</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">printStackTrace</span><span style="color:#89DDFF;">();</span></span>
<span class="line"><span style="color:#A6ACCD;">        </span><span style="color:#89DDFF;">}</span></span>
<span class="line"></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#89DDFF;">}</span></span>
<span class="line"><span style="color:#89DDFF;">}</span></span></code></pre></div><p>服务端监听线程：</p><div class="language-java"><button title="Copy Code" class="copy"></button><span class="lang">java</span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#676E95;font-style:italic;">// 开启服务监听线程,当收到连接请求后,开启新的线程进行处理</span></span>
<span class="line"><span style="color:#C792EA;">public</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">class</span><span style="color:#A6ACCD;"> </span><span style="color:#FFCB6B;">ServerThread</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">implements</span><span style="color:#A6ACCD;"> </span><span style="color:#FFCB6B;">Runnable</span><span style="color:#89DDFF;">{</span></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#89DDFF;">@</span><span style="color:#C792EA;">Override</span></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#C792EA;">public</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">void</span><span style="color:#A6ACCD;"> </span><span style="color:#82AAFF;">run</span><span style="color:#89DDFF;">()</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">{</span></span>
<span class="line"><span style="color:#A6ACCD;">        </span><span style="color:#89DDFF;font-style:italic;">try</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">{</span></span>
<span class="line"><span style="color:#A6ACCD;">            </span><span style="color:#C792EA;">ServerSocket</span><span style="color:#A6ACCD;"> serverSocket </span><span style="color:#89DDFF;">=</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;font-style:italic;">new</span><span style="color:#A6ACCD;"> </span><span style="color:#82AAFF;">ServerSocket</span><span style="color:#89DDFF;">(</span><span style="color:#A6ACCD;">Constant</span><span style="color:#89DDFF;">.</span><span style="color:#A6ACCD;">PORT</span><span style="color:#89DDFF;">);</span></span>
<span class="line"><span style="color:#A6ACCD;">            </span><span style="color:#89DDFF;font-style:italic;">while</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">(true){</span></span>
<span class="line"><span style="color:#A6ACCD;">                </span><span style="color:#C792EA;">Socket</span><span style="color:#A6ACCD;"> socket </span><span style="color:#89DDFF;">=</span><span style="color:#A6ACCD;"> serverSocket</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">accept</span><span style="color:#89DDFF;">();</span></span>
<span class="line"><span style="color:#A6ACCD;">                </span><span style="color:#89DDFF;font-style:italic;">new</span><span style="color:#A6ACCD;"> </span><span style="color:#82AAFF;">Thread</span><span style="color:#89DDFF;">(</span><span style="color:#89DDFF;font-style:italic;">new</span><span style="color:#A6ACCD;"> </span><span style="color:#82AAFF;">ServerProcessThread</span><span style="color:#89DDFF;">(</span><span style="color:#A6ACCD;">socket</span><span style="color:#89DDFF;">)).</span><span style="color:#82AAFF;">start</span><span style="color:#89DDFF;">();</span></span>
<span class="line"><span style="color:#89DDFF;">                </span><span style="color:#676E95;font-style:italic;">//开启新的线程进行连接请求的处理</span></span>
<span class="line"><span style="color:#A6ACCD;">            </span><span style="color:#89DDFF;">}</span></span>
<span class="line"><span style="color:#A6ACCD;">        </span><span style="color:#89DDFF;">}</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;font-style:italic;">catch</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">(</span><span style="color:#C792EA;">IOException</span><span style="color:#A6ACCD;"> </span><span style="color:#A6ACCD;font-style:italic;">e</span><span style="color:#89DDFF;">)</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">{</span></span>
<span class="line"><span style="color:#A6ACCD;">            e</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">printStackTrace</span><span style="color:#89DDFF;">();</span></span>
<span class="line"><span style="color:#A6ACCD;">        </span><span style="color:#89DDFF;">}</span></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#89DDFF;">}</span></span>
<span class="line"><span style="color:#89DDFF;">}</span></span></code></pre></div><p>服务端处理线程：</p><div class="language-java"><button title="Copy Code" class="copy"></button><span class="lang">java</span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#F78C6C;">import</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">java</span><span style="color:#89DDFF;">.</span><span style="color:#C792EA;">io</span><span style="color:#89DDFF;">.</span><span style="color:#A6ACCD;">*</span><span style="color:#89DDFF;">;</span></span>
<span class="line"><span style="color:#F78C6C;">import</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">java</span><span style="color:#89DDFF;">.</span><span style="color:#C792EA;">net</span><span style="color:#89DDFF;">.</span><span style="color:#C792EA;">Socket</span><span style="color:#89DDFF;">;</span></span>
<span class="line"><span style="color:#676E95;font-style:italic;">/**</span></span>
<span class="line"><span style="color:#676E95;font-style:italic;"> * 服务端收到连接请求后,处理请求的线程,阻塞式IO</span></span>
<span class="line"><span style="color:#676E95;font-style:italic;"> */</span></span>
<span class="line"><span style="color:#C792EA;">public</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">class</span><span style="color:#A6ACCD;"> </span><span style="color:#FFCB6B;">ServerProcessThread</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">implements</span><span style="color:#A6ACCD;"> </span><span style="color:#FFCB6B;">Runnable</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">{</span></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#C792EA;">private</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">Socket</span><span style="color:#A6ACCD;"> socket</span><span style="color:#89DDFF;">;</span></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#C792EA;">public</span><span style="color:#A6ACCD;"> </span><span style="color:#82AAFF;">ServerProcessThread</span><span style="color:#89DDFF;">(</span><span style="color:#C792EA;">Socket</span><span style="color:#A6ACCD;"> </span><span style="color:#A6ACCD;font-style:italic;">socket</span><span style="color:#89DDFF;">){</span></span>
<span class="line"><span style="color:#A6ACCD;">        </span><span style="color:#89DDFF;">this.</span><span style="color:#A6ACCD;">socket </span><span style="color:#89DDFF;">=</span><span style="color:#A6ACCD;"> socket</span><span style="color:#89DDFF;">;</span></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#89DDFF;">}</span></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#89DDFF;">@</span><span style="color:#C792EA;">Override</span></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#C792EA;">public</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">void</span><span style="color:#A6ACCD;"> </span><span style="color:#82AAFF;">run</span><span style="color:#89DDFF;">()</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">{</span></span>
<span class="line"><span style="color:#89DDFF;">        </span><span style="color:#676E95;font-style:italic;">//获取客户端的数据,并写回</span></span>
<span class="line"><span style="color:#89DDFF;">        </span><span style="color:#676E95;font-style:italic;">//等待响应</span></span>
<span class="line"><span style="color:#A6ACCD;">        </span><span style="color:#89DDFF;font-style:italic;">try</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">{</span></span>
<span class="line"><span style="color:#A6ACCD;">            </span><span style="color:#C792EA;">BufferedReader</span><span style="color:#A6ACCD;"> bufferedReader </span><span style="color:#89DDFF;">=</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;font-style:italic;">new</span><span style="color:#A6ACCD;"> </span><span style="color:#82AAFF;">BufferedReader</span><span style="color:#89DDFF;">(</span><span style="color:#89DDFF;font-style:italic;">new</span><span style="color:#A6ACCD;">   </span><span style="color:#82AAFF;">InputStreamReader</span><span style="color:#89DDFF;">(</span><span style="color:#A6ACCD;">socket</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">getInputStream</span><span style="color:#89DDFF;">()));</span></span>
<span class="line"><span style="color:#A6ACCD;">            </span><span style="color:#C792EA;">String</span><span style="color:#A6ACCD;"> line </span><span style="color:#89DDFF;">=</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">&quot;&quot;</span><span style="color:#89DDFF;">;</span></span>
<span class="line"><span style="color:#A6ACCD;">            </span><span style="color:#C792EA;">String</span><span style="color:#A6ACCD;"> requestStr </span><span style="color:#89DDFF;">=</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">&quot;&quot;</span><span style="color:#89DDFF;">;</span></span>
<span class="line"><span style="color:#A6ACCD;">            System</span><span style="color:#89DDFF;">.</span><span style="color:#A6ACCD;">out</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">println</span><span style="color:#89DDFF;">(</span><span style="color:#89DDFF;">&quot;</span><span style="color:#C3E88D;">来自客户端的数据:</span><span style="color:#89DDFF;">&quot;</span><span style="color:#89DDFF;">);</span><span style="color:#A6ACCD;"> </span><span style="color:#676E95;font-style:italic;">// 读取客户端数据</span></span>
<span class="line"><span style="color:#A6ACCD;">            </span><span style="color:#89DDFF;font-style:italic;">while</span><span style="color:#89DDFF;">((</span><span style="color:#A6ACCD;">line </span><span style="color:#89DDFF;">=</span><span style="color:#A6ACCD;"> bufferedReader</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">readLine</span><span style="color:#89DDFF;">())</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">!=</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">null){</span></span>
<span class="line"><span style="color:#A6ACCD;">                requestStr </span><span style="color:#89DDFF;">+=</span><span style="color:#A6ACCD;"> line</span><span style="color:#89DDFF;">;</span></span>
<span class="line"><span style="color:#A6ACCD;">                System</span><span style="color:#89DDFF;">.</span><span style="color:#A6ACCD;">out</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">println</span><span style="color:#89DDFF;">(</span><span style="color:#A6ACCD;">line</span><span style="color:#89DDFF;">);</span></span>
<span class="line"><span style="color:#A6ACCD;">            </span><span style="color:#89DDFF;">}</span></span>
<span class="line"><span style="color:#89DDFF;">            </span><span style="color:#676E95;font-style:italic;">//  从服务端发给客户端数据</span></span>
<span class="line"><span style="color:#A6ACCD;">            </span><span style="color:#C792EA;">Writer</span><span style="color:#A6ACCD;"> writer </span><span style="color:#89DDFF;">=</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;font-style:italic;">new</span><span style="color:#A6ACCD;"> </span><span style="color:#82AAFF;">OutputStreamWriter</span><span style="color:#89DDFF;">(</span><span style="color:#A6ACCD;">socket</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">getOutputStream</span><span style="color:#89DDFF;">());</span></span>
<span class="line"><span style="color:#A6ACCD;">            writer</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">write</span><span style="color:#89DDFF;">(</span><span style="color:#89DDFF;">&quot;</span><span style="color:#C3E88D;">data from server </span><span style="color:#89DDFF;">&quot;</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">+</span><span style="color:#A6ACCD;"> requestStr </span><span style="color:#89DDFF;">+</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">&quot;</span><span style="color:#A6ACCD;">\r\n</span><span style="color:#89DDFF;">&quot;</span><span style="color:#89DDFF;">);</span></span>
<span class="line"><span style="color:#A6ACCD;">            writer</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">flush</span><span style="color:#89DDFF;">();</span></span>
<span class="line"><span style="color:#A6ACCD;">            writer</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">close</span><span style="color:#89DDFF;">();</span></span>
<span class="line"><span style="color:#A6ACCD;">            bufferedReader</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">close</span><span style="color:#89DDFF;">();</span></span>
<span class="line"><span style="color:#A6ACCD;">            socket</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">close</span><span style="color:#89DDFF;">();</span></span>
<span class="line"><span style="color:#A6ACCD;">        </span><span style="color:#89DDFF;">}</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;font-style:italic;">catch</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">(</span><span style="color:#C792EA;">IOException</span><span style="color:#A6ACCD;"> </span><span style="color:#A6ACCD;font-style:italic;">e</span><span style="color:#89DDFF;">)</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">{</span></span>
<span class="line"><span style="color:#A6ACCD;">            e</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">printStackTrace</span><span style="color:#89DDFF;">();</span></span>
<span class="line"><span style="color:#A6ACCD;">        </span><span style="color:#89DDFF;">}</span></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#89DDFF;">}</span></span>
<span class="line"><span style="color:#89DDFF;">}</span></span></code></pre></div><p>客户端：</p><div class="language-java"><button title="Copy Code" class="copy"></button><span class="lang">java</span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#676E95;font-style:italic;">/**</span></span>
<span class="line"><span style="color:#676E95;font-style:italic;"> * 维护客户端socket连接的线程,阻塞式IO</span></span>
<span class="line"><span style="color:#676E95;font-style:italic;"> */</span></span>
<span class="line"><span style="color:#C792EA;">public</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">class</span><span style="color:#A6ACCD;"> </span><span style="color:#FFCB6B;">ClientProcessThread</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">implements</span><span style="color:#A6ACCD;"> </span><span style="color:#FFCB6B;">Runnable</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">{</span></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#C792EA;">private</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">Socket</span><span style="color:#A6ACCD;"> socket</span><span style="color:#89DDFF;">;</span></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#C792EA;">public</span><span style="color:#A6ACCD;"> </span><span style="color:#82AAFF;">ClientProcessThread</span><span style="color:#89DDFF;">(</span><span style="color:#C792EA;">Socket</span><span style="color:#A6ACCD;"> </span><span style="color:#A6ACCD;font-style:italic;">socket</span><span style="color:#89DDFF;">){</span></span>
<span class="line"><span style="color:#A6ACCD;">        </span><span style="color:#89DDFF;">this.</span><span style="color:#A6ACCD;">socket </span><span style="color:#89DDFF;">=</span><span style="color:#A6ACCD;"> socket</span><span style="color:#89DDFF;">;</span></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#89DDFF;">}</span></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#89DDFF;">@</span><span style="color:#C792EA;">Override</span></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#C792EA;">public</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">void</span><span style="color:#A6ACCD;"> </span><span style="color:#82AAFF;">run</span><span style="color:#89DDFF;">()</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">{</span></span>
<span class="line"><span style="color:#89DDFF;">        </span><span style="color:#676E95;font-style:italic;">//写数据,等待响应,输出响应</span></span>
<span class="line"><span style="color:#A6ACCD;">        </span><span style="color:#C792EA;">String</span><span style="color:#A6ACCD;"> requestStr </span><span style="color:#89DDFF;">=</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">&quot;</span><span style="color:#C3E88D;">data from client </span><span style="color:#A6ACCD;">\r\n</span><span style="color:#89DDFF;">&quot;</span><span style="color:#89DDFF;">;</span></span>
<span class="line"><span style="color:#A6ACCD;">        </span><span style="color:#89DDFF;font-style:italic;">try</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">{</span></span>
<span class="line"><span style="color:#A6ACCD;">            </span><span style="color:#C792EA;">Writer</span><span style="color:#A6ACCD;"> writer </span><span style="color:#89DDFF;">=</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;font-style:italic;">new</span><span style="color:#A6ACCD;"> </span><span style="color:#82AAFF;">OutputStreamWriter</span><span style="color:#89DDFF;">(</span><span style="color:#A6ACCD;">socket</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">getOutputStream</span><span style="color:#89DDFF;">());</span></span>
<span class="line"><span style="color:#A6ACCD;">            writer</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">write</span><span style="color:#89DDFF;">(</span><span style="color:#A6ACCD;">requestStr</span><span style="color:#89DDFF;">);</span></span>
<span class="line"><span style="color:#A6ACCD;">            writer</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">flush</span><span style="color:#89DDFF;">();</span></span>
<span class="line"><span style="color:#A6ACCD;">            socket</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">shutdownOutput</span><span style="color:#89DDFF;">();</span></span>
<span class="line"><span style="color:#89DDFF;">            </span><span style="color:#676E95;font-style:italic;">//等待响应</span></span>
<span class="line"><span style="color:#A6ACCD;">            </span><span style="color:#C792EA;">BufferedReader</span><span style="color:#A6ACCD;"> bufferedReader </span><span style="color:#89DDFF;">=</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;font-style:italic;">new</span><span style="color:#A6ACCD;"> </span><span style="color:#82AAFF;">BufferedReader</span><span style="color:#89DDFF;">(</span><span style="color:#89DDFF;font-style:italic;">new</span><span style="color:#A6ACCD;"> </span><span style="color:#82AAFF;">InputStreamReader</span><span style="color:#89DDFF;">(</span><span style="color:#A6ACCD;">socket</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">getInputStream</span><span style="color:#89DDFF;">()));</span></span>
<span class="line"><span style="color:#A6ACCD;">            </span><span style="color:#C792EA;">String</span><span style="color:#A6ACCD;"> line</span><span style="color:#89DDFF;">;</span></span>
<span class="line"><span style="color:#A6ACCD;">            System</span><span style="color:#89DDFF;">.</span><span style="color:#A6ACCD;">out</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">println</span><span style="color:#89DDFF;">(</span><span style="color:#89DDFF;">&quot;</span><span style="color:#C3E88D;">来自服务端的响应:</span><span style="color:#89DDFF;">&quot;</span><span style="color:#89DDFF;">);</span></span>
<span class="line"><span style="color:#A6ACCD;">            </span><span style="color:#89DDFF;font-style:italic;">while</span><span style="color:#89DDFF;">((</span><span style="color:#A6ACCD;">line </span><span style="color:#89DDFF;">=</span><span style="color:#A6ACCD;"> bufferedReader</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">readLine</span><span style="color:#89DDFF;">())</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">!=</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">null){</span></span>
<span class="line"><span style="color:#A6ACCD;">                System</span><span style="color:#89DDFF;">.</span><span style="color:#A6ACCD;">out</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">println</span><span style="color:#89DDFF;">(</span><span style="color:#A6ACCD;">line</span><span style="color:#89DDFF;">);</span></span>
<span class="line"><span style="color:#A6ACCD;">            </span><span style="color:#89DDFF;">}</span></span>
<span class="line"><span style="color:#A6ACCD;">            writer</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">close</span><span style="color:#89DDFF;">();</span></span>
<span class="line"><span style="color:#A6ACCD;">            bufferedReader</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">close</span><span style="color:#89DDFF;">();</span></span>
<span class="line"><span style="color:#A6ACCD;">            socket</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">close</span><span style="color:#89DDFF;">();</span></span>
<span class="line"></span>
<span class="line"><span style="color:#A6ACCD;">        </span><span style="color:#89DDFF;">}</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;font-style:italic;">catch</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">(</span><span style="color:#C792EA;">IOException</span><span style="color:#A6ACCD;"> </span><span style="color:#A6ACCD;font-style:italic;">e</span><span style="color:#89DDFF;">)</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">{</span></span>
<span class="line"><span style="color:#A6ACCD;">            e</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">printStackTrace</span><span style="color:#89DDFF;">();</span></span>
<span class="line"><span style="color:#A6ACCD;">        </span><span style="color:#89DDFF;">}</span></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#89DDFF;">}</span></span>
<span class="line"><span style="color:#89DDFF;">}</span></span></code></pre></div><p>输出结果：</p><img src="https://edu-8673.oss-cn-beijing.aliyuncs.com/img2022.6.30/202207201653780.png" alt="image-20220720165308738" style="zoom:67%;"><h3 id="_2-nio" tabindex="-1">2 NIO <a class="header-anchor" href="#_2-nio" aria-label="Permalink to &quot;2 NIO&quot;">​</a></h3><p><strong>同步非阻塞IO之NIO</strong>：服务器端保存一个Socket连接列表，然后对这个列表进行轮询，如果发现某个Socket端口上有数据可读时说明读就绪，则调用该socket连接的相应读操作。如果发现某个 Socket端口上有数据可写时说明写就绪，则调用该socket连接的相应写操作。如果某个端口的Socket连接已经中断，则调用相应的析构方法关闭该端口。这样能充分利用服务器资源，效率得到了很大提高，在进行IO操作请求时候再用个线程去处理，是<code>一个请求一个线程</code>。Java中使用Selector、Channel、Buffer来实现上述效果。</p><img src="https://edu-8673.oss-cn-beijing.aliyuncs.com/img2022.6.30/202207201653957.png" alt="image-20220720165325901" style="zoom:67%;"> 每个线程中包含一个`Selector`对象，它相当于一个通道管理器，可以实现在一个线程中处理多个通道的目的，减少线程的创建数量。远程连接对应一个channel，数据的读写通过buffer均在同一个`channel`中完成，并且数据的读写是非阻塞的。通道创建后需要注册在`selector`中，同时需要为该通道注册感兴趣事件（客户端连接服务端事件、服务端接收客户端连接事件、读事件、写事件)，`selector`线程需要采用`轮训`的方式调用`selector`的`select`函数，直到所有注册通道中有兴趣的事件发生，则返回，否则一直阻塞。而后循环处理所有就绪的感兴趣事件。以上步骤解决BIO的两个瓶颈： <ol><li>不必对每个连接分别创建线程。</li><li>数据读写非阻塞。</li></ol><p>下面对以下三个概念做一个简单介绍，Java NIO由以下三个核心部分组成：</p><ol><li><code>selector</code>：Selector 允许单线程处理多个Channel。如果你的应用打开了多个连接（通道），但每个连接的流量都很低，使用Selector就会很方便。要使用Selector，得向Selector注册Channel，然后调用他的select方法，这个方法会一直阻塞到某个注册的通道有事件就绪。一旦这个方法返回，线程就可以处理这些事件，事件的例子入有新连接接进来，数据接收等。</li><li><strong><code>Channel</code></strong>：基本上所有的IO在NIO中都从一个Channel开始。Channel有点像流，数据可以从channel<strong>读</strong>到buffer，也可以从buffer<strong>写</strong>到channel。</li><li><strong><code>Buffer</code></strong>：缓冲区本质上是一个可以读写数据的内存块，可以理解成是一个容器对象(含数组)，该对象提供了一组方法，可以更轻松的使用内存块，缓冲区对象内置了一些机制，能够跟踪和记录缓冲区的状态变换情况，Channel提供从文件，网络读取数据的渠道，但是读取或者写入的数据都必须经由Buffer。</li></ol><p>channel和buffer有好几种类型。下面是Java NIO中的一些主要channel的实现：</p><div class="language-c"><button title="Copy Code" class="copy"></button><span class="lang">c</span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#A6ACCD;">FileChannel</span></span>
<span class="line"><span style="color:#A6ACCD;">DatagramChannel</span></span>
<span class="line"><span style="color:#A6ACCD;">SocketChannel</span></span>
<span class="line"><span style="color:#A6ACCD;">ServerSocketChannel</span></span></code></pre></div><p>正如你所看到的，这些通道涵盖了UDP和TCP网络IO，以及文件IO。以下是Java NIO里关键的buffer实现：</p><div class="language-c"><button title="Copy Code" class="copy"></button><span class="lang">c</span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#A6ACCD;">ByteBuffer</span></span>
<span class="line"><span style="color:#A6ACCD;">CharBuffer</span></span>
<span class="line"><span style="color:#A6ACCD;">FloatBuffer</span></span>
<span class="line"><span style="color:#A6ACCD;">IntBuffer</span></span>
<span class="line"><span style="color:#A6ACCD;">LongBuffer</span></span>
<span class="line"><span style="color:#A6ACCD;">ShortBuffer</span></span></code></pre></div><p>在微服务阶段，一个请求可能涉及到多个不同服务之间的跨服务器调用，如果你想实现高性能的PRC框架来进行数据传输，那就可以基于Java NIO做个支持长连接、自定义协议、高并发的框架，比如Netty。Netty本身就是一个基于NIO的网络框架， 封装了Java NIO那些复杂的底层细节，给你提供简单好用的抽象概念来编程。比如<a href="https://mp.weixin.qq.com/s?__biz=MzI4NjI1OTI4Nw==&amp;mid=2247489782&amp;idx=1&amp;sn=efbe773e62b12ad291d7801e3affc014&amp;scene=21#wechat_redirect" target="_blank" rel="noreferrer">Dubbo</a>底层就是用的Netty。</p><img src="https://edu-8673.oss-cn-beijing.aliyuncs.com/img2022.6.30/202207201654144.png" alt="image-20220720165404091" style="zoom:67%;"><h3 id="_3-aio" tabindex="-1">3 AIO <a class="header-anchor" href="#_3-aio" aria-label="Permalink to &quot;3 AIO&quot;">​</a></h3><p>AIO是异步非阻塞IO，相比NIO更进一步，进程读取数据时只负责发送跟接收指令，数据的准备工作完全由操作系统来处理。</p><h1 id="零拷贝和nio" tabindex="-1">零拷贝和NIO <a class="header-anchor" href="#零拷贝和nio" aria-label="Permalink to &quot;零拷贝和NIO&quot;">​</a></h1><h2 id="前言-1" tabindex="-1">前言 <a class="header-anchor" href="#前言-1" aria-label="Permalink to &quot;前言&quot;">​</a></h2><p>&#39;零拷贝&#39;这个词大家应该不陌生了，也算是大厂面试中的一个高频考点，玩过 NETTY 的朋友应该对此相当熟悉了，NETTY 的**「高并发」**很大程度上都是因为 NIO，而 NIO 的核心就是零拷贝技术了，今天就让你十分钟玩懂零拷贝。</p><h2 id="传统的io模型是怎么样的" tabindex="-1">传统的IO模型是怎么样的？ <a class="header-anchor" href="#传统的io模型是怎么样的" aria-label="Permalink to &quot;传统的IO模型是怎么样的？&quot;">​</a></h2><p>我们来看一张图，让我们看看一个文件从磁盘传输到网卡究竟要经历什么样的磨难：</p><img src="https://edu-8673.oss-cn-beijing.aliyuncs.com/img2022.6.30/202207202117380.png" alt="image-20220720211700268" style="zoom:50%;"><ul><li><strong>「第一步」</strong>：将文件通过 <strong>「DMA」</strong> 技术从磁盘中拷贝到内核缓冲区</li><li><strong>「第二步」</strong>：将文件从内核缓冲区拷贝到用户进程缓冲区域中</li><li><strong>「第三步」</strong>：将文件从用户进程缓冲区中拷贝到 socket 缓冲区中</li><li><strong>「第四步」</strong>：将socket缓冲区中的文件通过 <strong>「DMA」</strong> 技术拷贝到网卡</li></ul><p>这种数据存储的区域整体我们把它叫做**「非直接缓冲区」**。</p><p>我们发现，居然有四步数据拷贝的过程！！并且整个数据的传输过程都是**「需要 CPU 去执行」**的。</p><p>这个过程也太繁琐了，我就想传输一些数据，干嘛要传到用户这里，还要我自己再走一遍后续的流程，写到 socket 缓冲区再发出去，你不能帮我实现吗？</p><h2 id="怎么去优化传统-io-的流程呢" tabindex="-1">怎么去优化传统 IO 的流程呢？ <a class="header-anchor" href="#怎么去优化传统-io-的流程呢" aria-label="Permalink to &quot;怎么去优化传统 IO 的流程呢？&quot;">​</a></h2><p>我们继续看上面的流程图理一下，看看哪些步骤是可以去掉的</p><p>我们发现在整个过程中，数据从磁盘读出来到发送给网卡，**「文件内容」<strong>都是</strong>「不会发生改变」<strong>的，但是我却要经历</strong>「4次文件内容的拷贝」**才真正能将文件传输到网卡。</p><p>那么以最简单的的方式来说，<strong>「能不能直接将磁盘中的数据传输到网卡呢？」</strong></p><p>当然不可以，这个原因也很简单，因为**「网卡和磁盘都是外部设备」**，所以一定要有一个中间的缓冲区域来取存储数据，做一个转发的作用。</p><img src="https://edu-8673.oss-cn-beijing.aliyuncs.com/img2022.6.30/202207202117284.png" alt="image-20220720211735180" style="zoom:50%;"><p>那么我们看上图中能做缓冲的有两个区域，一个是 <strong>「socket缓冲区」</strong>，一个是**「内核缓冲区」**，那么用哪一个？</p><p>这个问题应该很好选择了，socket 肯定不可以，socket 和我操作系统无瓜，那么只有用内核缓冲区来做缓冲区。</p><p>那么能不能通过**「内核缓冲区直接给网卡」**发送数据呢？</p><p>看样子是可以的，那么我们来看看，socket 缓冲区的作用是什么？</p><h2 id="socket缓冲区的作用" tabindex="-1">socket缓冲区的作用 <a class="header-anchor" href="#socket缓冲区的作用" aria-label="Permalink to &quot;socket缓冲区的作用&quot;">​</a></h2><p>每个 socket 被创建后，都会分配两个缓冲区，输入缓冲区和输出缓冲区。</p><p>write()/send() 并不立即向网络中传输数据，而是先将数据写入缓冲区中，再由 TCP 协议将数据从缓冲区发送到目标机器。一旦将数据写入到缓冲区，函数就可以成功返回，不管它们有没有到达目标机器，也不管它们何时被发送到网络，这些都是 TCP 协议负责的事情。</p><p>所以socket就是用来**「传输网络数据」**的，看来没它还不行。</p><p>但是我们换个思路，是不是说，只需要**「告诉 socket 要传输哪些数据」**就可以了？然后文件内容就可以直接用内核缓冲区的就好了。</p><h2 id="零拷贝-zero-copy-是怎么做到性能提升" tabindex="-1">零拷贝（zero copy）是怎么做到性能提升 <a class="header-anchor" href="#零拷贝-zero-copy-是怎么做到性能提升" aria-label="Permalink to &quot;零拷贝（zero copy）是怎么做到性能提升&quot;">​</a></h2><p>当你读懂了上面的内容，基本上已经能摸到零拷贝的核心脉络了，其实零拷贝就是使用**「内存映射」**来消除数据拷贝次数的，然后使用 <strong>「DMA」</strong> 技术来减少CPU的工作时间。</p><p>就只从拷贝次数的性能来看，可以讲性能提高至少百分之五十以上。</p><h2 id="dma" tabindex="-1">DMA <a class="header-anchor" href="#dma" aria-label="Permalink to &quot;DMA&quot;">​</a></h2><p>上文中经常提到一个很重要的词汇 - DMA ，它在整个零拷贝的流程当中是有很大的占比的，<strong>「能帮助 CPU 做大量的工作」</strong>，我们来介绍一下这个神奇的技术。</p><p>DMA就是**「直接存储器访问」<strong>,DMA (Direct Memory Access，</strong>「直接存储器访问」**) 是所有现代电脑的重要特色，它允许不同速度的硬件装置来沟通，而不需要依赖于 CPU 的大量中断负载。否则，CPU 需要从来源把每一片段的资料复制到暂存器，然后把它们再次写回到新的地方。在这个时间中，CPU 对于其他的工作来说就无法使用。</p><p><strong>「原理」</strong>：DMA 传输将数据**「从一个地址空间复制到另外一个地址空间」**。当 CPU 初始化这个传输动作，传输动作本身是由 DMA 控制器来实行和完成。</p><h2 id="零拷贝整体流程图" tabindex="-1">零拷贝整体流程图 <a class="header-anchor" href="#零拷贝整体流程图" aria-label="Permalink to &quot;零拷贝整体流程图&quot;">​</a></h2><img src="https://edu-8673.oss-cn-beijing.aliyuncs.com/img2022.6.30/202207202118931.png" alt="image-20220720211804821" style="zoom:50%;"><p>看到这里的话相信你对零拷贝已经有了深刻的理解，那么 NIO 到底是什么的？既然说了十分钟让你玩懂 NIO 和零拷贝，那 NIO 必不可少。</p><h2 id="为什么需要-nio" tabindex="-1">为什么需要 NIO ？ <a class="header-anchor" href="#为什么需要-nio" aria-label="Permalink to &quot;为什么需要 NIO ？&quot;">​</a></h2><p>所有的系统I/O都分为**「两个阶段」**：</p><ul><li>1.等待就绪</li><li>2.读写操作</li></ul><p>需要说明的是等待就绪的阻塞是不使用CPU的，是在“<strong>「空等」</strong>”；而真正的读写操作的阻塞是使用CPU的，真正在”干活”，而且这个过程非常快，属于memory copy，带宽通常在1GB/s级别以上，可以理解为**「基本不耗时」**。</p><h3 id="我们先来看看传统io是怎么做的" tabindex="-1">我们先来看看传统IO是怎么做的 <a class="header-anchor" href="#我们先来看看传统io是怎么做的" aria-label="Permalink to &quot;我们先来看看传统IO是怎么做的&quot;">​</a></h3><p>在传统的 socket IO中，需要为每个连接创建一个线程。</p><p><strong>「一个线程对应一个连接，只处理一个连接的事情」</strong>，这就是传统的socket IO。</p><p>当**「并发的连接数量非常巨大」<strong>时，线程所占用的栈内存和CPU线程</strong>「切换的开销就会非常大」**。</p><img src="https://edu-8673.oss-cn-beijing.aliyuncs.com/img2022.6.30/202207202118577.png" alt="image-20220720211831473" style="zoom:50%;"><p>在这种情境下还可能会出现**「线程数量小于连接数量」**的情况，所以每个线程进行 I O操作时就不能阻塞，如果阻塞的话，有些连接就得不到处理。</p><p>如上图，假设有三条线程在管理三条连接，如果此时有第四个任务插入，那么就只能等待前面任务执行完成。</p><img src="https://edu-8673.oss-cn-beijing.aliyuncs.com/img2022.6.30/202207202118833.png" alt="image-20220720211841744" style="zoom:67%;"><p>其操作就像是一条流水线一样，是串行阻塞的，故传统 IO 我们也称为 <strong>「BIO」</strong>。</p><p>传统 IO 也**「不知道什么时候该处理数据」**，所以只能一直傻等。</p><p>为了解决这些问题，NIO 就出现了。</p><h2 id="nio-是-怎么解决这些问题的" tabindex="-1">NIO 是 怎么解决这些问题的？ <a class="header-anchor" href="#nio-是-怎么解决这些问题的" aria-label="Permalink to &quot;NIO 是 怎么解决这些问题的？&quot;">​</a></h2><p>我们先来介绍一下 NIO 的核心组件</p><ul><li><p>channel（通道）</p></li><li><ul><li>一个channel（通道）代表和某一实体的连接，这个实体可以是文件、网络套接字等。也就是说，通道是Java NIO提供的一座**「桥梁」**，用于我们的程序和操作系统底层I/O服务进行交互</li></ul></li><li><p>buffer（缓冲区）</p><img src="https://edu-8673.oss-cn-beijing.aliyuncs.com/img2022.6.30/202207202118697.png" alt="image-20220720211857633" style="zoom:67%;"></li><li><ul><li>你可以把它理解为存储数据的地方，buffer很重要的三个属性 capacity （总容量）,position （指针当前位置）,limit （读/写边界位置）</li></ul></li><li><p>selectors（选择器）</p></li><li><ul><li>selector（选择器）是一个特殊的组件，用于采集各个通道的状态（或者说事件）。我们先将**「通道注册到选择器，并设置好关心的事件」**，然后就可以通过调用select()方法，静静地等待事件发生。</li></ul></li></ul><p>通道有如下4个事件可供我们监听：</p><p>Accept：有可以接受的连接</p><p>Connect：连接成功</p><p>Read：有数据可读</p><p>Write：可以写入数据了</p><p>我们首先需要注册当这几个事件到来的时候所对应的处理器。然后在合适的时机告诉事件选择器：我对这个事件感兴趣。</p><img src="https://edu-8673.oss-cn-beijing.aliyuncs.com/img2022.6.30/202207202119437.png" alt="image-20220720211910337" style="zoom:67%;"><p>也就是说，在选择器上注册了这四个事件的处理器，用来处理 channel 的事件，<strong>「当 channel 某个事件真的准备就绪了，可以进行下一步的动作时，再告诉服务端来处理相应的数据，把相应的任务分配给服务端」</strong>，这样就能更好的利用 cpu 的资源。</p><p>前面我们说的零拷贝，就是在这时数据处理时发生的。</p><h2 id="nio-和-io-有什么区别" tabindex="-1">NIO 和 IO 有什么区别？ <a class="header-anchor" href="#nio-和-io-有什么区别" aria-label="Permalink to &quot;NIO 和 IO 有什么区别？&quot;">​</a></h2><ul><li>1.NIO是以**「缓冲区（块）」** 的方式处理数据，IO是以**「流」**的形式去写入和读出的。</li><li>2.NIO 又是基于这种流的形式，采用了通道和缓冲区的形式来进行处理数据的</li><li>3.还有一点就是 NIO 的通道是可以**「双向」<strong>的，但是 IO 中的流只能是</strong>「单向」**的</li><li>4.还有就是 NIO 的缓冲区还可以进行分片，可以建立**「只读缓冲区、直接缓冲区和间接缓冲区」**，直接缓冲区是为加快 I/O 速度，而以一种特殊的方式分配其内存的缓冲区</li><li>5.读写触发方式不同，NIO 是以选择器的轮询机制来触发的， IO是收到信息即触发。</li></ul><h2 id="总结-2" tabindex="-1">总结 <a class="header-anchor" href="#总结-2" aria-label="Permalink to &quot;总结&quot;">​</a></h2><p>从传统 IO 模型 到 NIO 零拷贝模型我们可以看出，一个新技术的产生到崛起肯定是因为**「其能满足之前技术满足不了的需求，或者相对于之前技术的性能有很高的提升」**。</p><p>传统 IO 传输需要进行四次的数据内容拷贝，包括**「内核态和用户态」<strong>的切换，</strong>「内核态和数据载体」**（磁盘、网卡）的切换，整个过程是阻塞的，过程浪费了很多资源。</p><p>而 NIO 是通过选择器，通道等核心模块，将整个 IO 处理过程变为异步的方式，只有其数据任务真正就绪了，才会让 cpu 去做处理，大量的节省了资源，提高了性能。</p><p>零拷贝就是让用户态和内核态之间的数据不再通过拷贝的方式传输，使用了**「内存映射」**，做到了内核态和用户态数据的零拷贝。</p><p>其拷贝方式使用了 <strong>「DMA」</strong> 技术，其目的就是为了解决 CPU 拷贝数据的方式，让**「拷贝数据」<strong>这种累活</strong>「不再占用 CPU 的资源」**，有 DMA 去完成。</p><p>因为是使用了内存映射的关系，所以零拷贝技术**「无法对数据内容做更改」**。</p><h1 id="cpu-和-gpu-有什么区别" tabindex="-1">CPU 和 GPU 有什么区别 <a class="header-anchor" href="#cpu-和-gpu-有什么区别" aria-label="Permalink to &quot;CPU 和 GPU 有什么区别&quot;">​</a></h1><h2 id="基本解释" tabindex="-1">基本解释 <a class="header-anchor" href="#基本解释" aria-label="Permalink to &quot;基本解释&quot;">​</a></h2><p>首先来看 CPU 和 GPU 的百科解释：</p><p>CPU（Central ProcessingUnit，中央处理器）：功能主要是解释计算机指令以及处理计算机软件中的数据</p><img src="https://mmbiz.qpic.cn/mmbiz_png/PocakShgoGGnEnNM1HzRpyTibZIFXzUEfSQOwD0dnAhLdJtk8W47xV9bWgKpFZFBpSAPgY69FDBRaIbAy6lwNtA/640?wx_fmt=png&amp;wxfrom=5&amp;wx_lazy=1&amp;wx_co=1" alt="图片" style="zoom:33%;"><p>GPU（Graphics ProcessingUnit，图形处理器；又称显示核心、显卡、视觉处理器、显示芯片或绘图芯片）：GPU 不同于传统的 CPU，如Intel i5 或 i7 处理器，其内核数量较少，专为通用计算而设计。相反，GPU是一种特殊类型的处理器，具有数百或数千个内核，经过优化，可并行运行大量计算。虽然 GPU 在游戏中以 3D 渲染而闻名，但它们对运行分析、深度学习和机器学习算法尤其有用。GPU 允许某些计算比传统 CPU 上运行相同的计算速度快 10 倍至 100 倍。</p><img src="https://mmbiz.qpic.cn/mmbiz_png/PocakShgoGGnEnNM1HzRpyTibZIFXzUEfjgic1uic8xP6VrhQA51FgxAqbsZQGq28FEw9qNQjN4HT4DRc78dbBeVQ/640?wx_fmt=png&amp;wxfrom=5&amp;wx_lazy=1&amp;wx_co=1" alt="图片" style="zoom:33%;"><p><strong>教授 vs 小学生</strong></p><p>我们可以简单地将 CPU 理解为学识渊博的教授，什么都精通，而 GPU 则是一堆小学生，只会简单的算数运算，可即使教授再神通广大，也不能一秒钟内计算出 500 次加减法，<strong>因此对简单重复的计算来说，单单一个教授敌不过数量众多的小学生</strong>，在进行简单的算数运算这件事上，500 个小学生（并发）可以轻而易举打败教授。</p><p><img src="https://mmbiz.qpic.cn/mmbiz_png/9BK2WL6kfcRXJEh4vVFVpISFwwaRAmv2mzJxibOzibhSWunYV7hNlic0b2GskKAstZaYiaPxWKXWYFjznlnxrc1cCA/640?wx_fmt=png&amp;wxfrom=5&amp;wx_lazy=1&amp;wx_co=1" alt="图片"></p><p>因此我们可以看到，CPU 和 GPU 的最大不同在于架构，CPU 适用于广泛的应用场景（学识渊博），可以执行任意程序，而 GPU 则专为多任务而生，并发能力强，具体来讲就是多核，一般的 CPU 有 2 核、4 核、8 核等，而 GPU 则可能会有成百上千核。</p><p><img src="https://mmbiz.qpic.cn/mmbiz_png/9BK2WL6kfcRXJEh4vVFVpISFwwaRAmv2aZgO3zGlNwbAJsBibczY5ZNtDlOZxPTL7BgOovx7aRfKJFBjL3DyF1w/640?wx_fmt=png&amp;wxfrom=5&amp;wx_lazy=1&amp;wx_co=1" alt="图片"></p><p>可以看到，CPU 内部 cache 以及控制部分占据了很大一部分片上面积，因此计算单元占比很少。再来看看 GPU，GPU 只有很简单的控制单元，剩下的大部分都被计算单元占据，因此 CPU 的核数有限，而 GPU 则轻松堆出上千核。</p><p><img src="https://mmbiz.qpic.cn/mmbiz_png/9BK2WL6kfcRXJEh4vVFVpISFwwaRAmv2IDAtOrzQaeHna4kOGQzdqKnZ1VGYur6zfXJ1t4wSQ3ZXg2LbkjtP1g/640?wx_fmt=png&amp;wxfrom=5&amp;wx_lazy=1&amp;wx_co=1" alt="图片"></p><p>只不过 CPU 中每个核的能力好比教授，而 GPU 的每个核的能力好比一个小学生。</p><p>你可能会想，为什么 GPU 需要这么奇怪的架构呢？</p><h2 id="应用场景" tabindex="-1">应用场景 <a class="header-anchor" href="#应用场景" aria-label="Permalink to &quot;应用场景&quot;">​</a></h2><p>CPU 和 GPU 之所以大不相同，是由于其设计目标的不同，它们分别针对了两种不同的应用场景：</p><blockquote><ul><li>CPU 需要很强的通用性来处理各种不同的数据类型，同时又要逻辑判断又会引入大量的分支跳转和中断的处理。这些都使得 CPU 的内部结构异常复杂</li><li>而 GPU 面对的则是类型高度统一的、相互无依赖的大规模数据和不需要被打断的纯净的计算环境</li></ul></blockquote><p>于是 CPU 和 GPU 就呈现出非常不同的架构（如下图 1-3 所示，图片来源 Nvidia），其中 GPU 部分的绿色是计算单元（ALU），橙红色是存储单元（Cache），橙黄色是控制单元（Control），DRAM 代表内存：</p><img src="https://edu-8673.oss-cn-beijing.aliyuncs.com/img2023.1.30/202212190958494.png" alt="image-20221219095847395" style="zoom:80%;"><p>由上图 1-3 可以看出：GPU 采用了数量众多的计算单元和超长的流水线，但只有非常简单的控制逻辑并省去了 Cache。而 CPU 不仅被 Cache 占据了大量空间，而且还有有复杂的控制逻辑和诸多优化电路，相比之下计算能力只是 CPU 很小的一部分。</p><h2 id="为什么-gpu-需要这么多核心" tabindex="-1">为什么 GPU 需要这么多核心？ <a class="header-anchor" href="#为什么-gpu-需要这么多核心" aria-label="Permalink to &quot;为什么 GPU 需要这么多核心？&quot;">​</a></h2><p>想一想计算机上的一张图是怎么表示的？无非就是屏幕上的一个个像素：</p><img src="https://mmbiz.qpic.cn/mmbiz_png/9BK2WL6kfcRXJEh4vVFVpISFwwaRAmv2eXEYw8XU6pM3YyeKTsaHeuTzHZBYTaWxhROOExOVl4ribKUN5HiaDw9w/640?wx_fmt=png&amp;wxfrom=5&amp;wx_lazy=1&amp;wx_co=1" alt="图片" style="zoom:50%;"><p>我们需要为每个像素进行计算，<strong>而且是相同的运算</strong>，就好比刚才例子中的小学生计算加减法一样，注意，对于屏幕来说一般会有上百万个像素，如果我们要串行地为每一个像素进行运算效率就太低了，因此我们可以让 GPU 中的每一个核心去为相应的像素进行计算，由于 GPU 中有很多核心，因此并行计算可以大幅提高速度。</p><p>现在你应该明白为什么 GPU 要这样工作了吧。除了 GPU 的核心数比较多之外，GPU 的工作方式也比较奇怪。</p><h2 id="cpu-和-gpu-的设计目标" tabindex="-1">CPU 和 GPU 的设计目标 <a class="header-anchor" href="#cpu-和-gpu-的设计目标" aria-label="Permalink to &quot;CPU 和 GPU 的设计目标&quot;">​</a></h2><p>由此我们引出 CPU 和 GPU 的设计目标：</p><p>1）CPU 是基于低延迟（Low Latency）的设计，内核数量较少</p><ul><li>Powerful ALU（强大的算术运算单元）：它可以在很少的时钟周期内完成算术计算；</li><li>Large Caches（大的缓存）：将部分数据保存在缓存中，使得长延迟的内存访问转换称短延迟的缓存访问；</li><li>Sophisticated Control（复杂的逻辑控制单元）：当程序含有多个分支的时候，它通过提供分支预测的能力来降低分支延时；并且，当一些指令依赖前面的指令结果时，它通过提供尽可能快的数据转发的能力来减少数据延迟。</li></ul><p>2）GPU 是基于大吞吐量（Big Throughput）的设计，内核数量较多</p><ul><li><p>Small Caches（小的缓存）：GPU 中缓存的目的不是保存后面需要访问的数据的，这点和 CPU 不同，而是为 Thread 提供服务的。如果有很多线程需要访问同一个相同的数据，缓存会合并这些访问，然后再去访问内存。但是由于需要访问内存，自然会带来延时的问题；</p><blockquote><p>批量读取/访问，一个非常常见的提升吞吐量的设计，比如 Kafka 中就用到了类似思想</p></blockquote></li><li><p>Simple Control（简单的逻辑控制单元）：把多个的访问合并成少的访问；</p></li><li><p>Energy efficient ALUs（大量的算术运算单元）：如上所述，GPU 虽然有内存延时，却有非常多的 ALU 并支持非常多的 Thread，因此，可以充分利用 ALU 尽可能多地分配线程从而达到非常大的吞吐量。</p></li></ul><p>总结来说，作为强大的执行引擎，CPU 将它数量相对较少的内核集中用于处理单个任务，并快速将其完成。这使它尤其适合用于处理逻辑控制、串行计算、数据库运行等类型的工作。</p><p>相比之下，GPU 由数百个内核组成，可以同时处理数千个线程，所以与 CPU 擅长、串行的运算和通用类型数据运算不同，GPU 擅长的是大规模并发计算，将复杂的问题分解成数千或数百万个独立的任务，并一次性解决它们，比如图像处理任务，包括纹理、灯光和形状渲染等子任务都必须同时完成，以保持图像在屏幕上快速呈现，除此之外，GPU 还被大量应用于深度学习、密码破解等任务中。比较适合 GPU 的计算场景是这样的：1)计算简单；2）重复计算。如果你的计算场景和这里的图像渲染相似，那么使用 GPU 就很合理了。因此对于图形图像计算、天气预报以及神经网络等都适用于 GPU，哦对了，GPU 还适合用来挖矿。</p><p>表 1.1 CPU 和 GPU 的区别</p><table><thead><tr><th style="text-align:center;">CPU</th><th style="text-align:center;">GPU</th></tr></thead><tbody><tr><td style="text-align:center;">Several cores</td><td style="text-align:center;">Many cores</td></tr><tr><td style="text-align:center;">Low latency</td><td style="text-align:center;">High throughput</td></tr><tr><td style="text-align:center;">Good for serial processing</td><td style="text-align:center;">Good for parallel processing</td></tr><tr><td style="text-align:center;">Can do a handful of operations at once</td><td style="text-align:center;">Can do thousands of operations at once</td></tr></tbody></table><p>下面用一个通俗的例子来做个比喻：</p><blockquote><p>注意只是比喻，可能不会太恰当，主要是帮助理解</p></blockquote><p>假设我们需要做一道鸡兔同笼的小学奥数题（来源 1500 年前的《孙子算经》）：</p><ul><li>今有雉兔同笼，上有三十五头，下有九十四足，问雉兔各几何？</li></ul><p>计算题目，理解题目并且整理出解题的步骤以及解法，这是 CPU 干的事情，于是 CPU 给出了类似下面的二元一次方程：</p><p><img src="https://mmbiz.qpic.cn/mmbiz_png/PocakShgoGGnEnNM1HzRpyTibZIFXzUEfMcsiaNs3lX2p538icTz5JyiaXt0GphVhtuanI4ArxibZB41xKjYdl0YrHw/640?wx_fmt=png&amp;wxfrom=5&amp;wx_lazy=1&amp;wx_co=1" alt="图片"></p><p>解题的过程需要用到的众多计算，则需要一帮不需要很高逻辑理解力的计算者完成，他们只需要负责其中很简单但是数量又很大的简单运算就行了，最后他们把各自运算的结果交出来给 CPU 整理，那么这群计算者就是 GPU。</p><p>简单来说就是：</p><blockquote><ul><li>CPU 力气大啥事都能干，还要协调手下各类小弟；</li><li>GPU 就是 CPU 的其中一个小弟，老大分配给给他处理图形或者并行计算等任务，这方面处理简单，但是量大，老大虽然能处理，可是精力有限（指 CPU 内核数量较少），所以不如交给小弟处理了，小弟精力旺盛（指 GPU 拥有大量内核），而且专门干这行，非常有经验，干起活儿来贼快。</li></ul></blockquote><h1 id="单核-cpu-支持-java-多线程吗" tabindex="-1">单核 CPU 支持 Java 多线程吗 <a class="header-anchor" href="#单核-cpu-支持-java-多线程吗" aria-label="Permalink to &quot;单核 CPU 支持 Java 多线程吗&quot;">​</a></h1><p>由于现在大多计算机都是多核CPU，多线程往往会比单线程更快，更能够提高并发，但提高并发并不意味着启动更多的线程来执行。更多的线程意味着线程创建销毁开销加大、上下文非常频繁，你的程序反而不能支持更高的TPS。</p><h2 id="时间片" tabindex="-1">时间片 <a class="header-anchor" href="#时间片" aria-label="Permalink to &quot;时间片&quot;">​</a></h2><p>多任务系统往往需要同时执行多道作业。作业数往往大于机器的CPU数，然而一颗CPU同时只能执行一项任务，如何让用户感觉这些任务正在同时进行呢? 操作系统的设计者 巧妙地利用了时间片轮转的方式</p><p>时间片是CPU分配给各个任务（线程）的时间！</p><blockquote><p>思考：单核CPU为何也支持多线程呢？</p></blockquote><p>线程上下文是指某一时间点 CPU 寄存器和程序计数器的内容，CPU通过时间片分配算法来循环执行任务（线程），因为时间片非常短，所以CPU通过不停地切换线程执行。</p><p>换言之，单CPU这么频繁，多核CPU一定程度上可以减少上下文切换。</p><h2 id="超线程" tabindex="-1">超线程 <a class="header-anchor" href="#超线程" aria-label="Permalink to &quot;超线程&quot;">​</a></h2><p>现代CPU除了处理器核心之外还包括寄存器、L1L2缓存这些存储设备、浮点运算单元、整数运算单元等一些辅助运算设备以及内部总线等。一个多核的CPU也就是一个CPU上有多个处理器核心，就意味着程序的不同线程需要经常在CPU之间的外部总线上通信，同时还要处理不同CPU之间不同缓存导致数据不一致的问题。</p><p>超线程这个概念是Intel提出的，简单来说是在一个CPU上真正的并发两个线程，由于CPU都是分时的（如果两个线程A和B，A正在使用处理器核心，B正在使用缓存或者其他设备，那AB两个线程就可以并发执行，但是如果AB都在访问同一个设备，那就只能等前一个线程执行完后一个线程才能执行）。实现这种并发的原理是 在CPU里加了一个协调辅助核心，根据Intel提供的数据，这样一个设备会使得设备面积增大5%，但是性能提高15%~30%。</p><h2 id="上下文切换" tabindex="-1">上下文切换 <a class="header-anchor" href="#上下文切换" aria-label="Permalink to &quot;上下文切换&quot;">​</a></h2><ul><li>线程切换，同一进程中的两个线程之间的切换</li><li>进程切换，两个进程之间的切换</li><li>模式切换，在给定线程中，用户模式和内核模式的切换</li><li>地址空间切换，将虚拟内存切换到物理内存</li></ul><p>CPU切换前把当前任务的状态保存下来，以便下次切换回这个任务时可以再次加载这个任务的状态，然后加载下一任务的状态并执行。任务的状态保存及再加载, 这段过程就叫做上下文切换。</p><p>每个线程都有一个程序计数器（记录要执行的下一条指令），一组寄存器（保存当前线程的工作变量），堆栈（记录执行历史，其中每一帧保存了一个已经调用但未返回的过程）。</p><p>寄存器 是 CPU 内部的数量较少但是速度很快的内存（与之对应的是 CPU 外部相对较慢的 RAM 主内存）。寄存器通过对常用值（通常是运算的中间值）的快速访问来提高计算机程序运行的速度。</p><p>程序计数器是一个专用的寄存器，用于表明指令序列中 CPU 正在执行的位置，存的值为正在执行的指令的位置或者下一个将要被执行的指令的位置。</p><ul><li>挂起当前任务（线程/进程），将这个任务在 CPU 中的状态（上下文）存储于内存中的某处</li><li>恢复一个任务（线程/进程），在内存中检索下一个任务的上下文并将其在 CPU 的寄存器中恢复</li><li>跳转到程序计数器所指向的位置（即跳转到任务被中断时的代码行），以恢复该进程在程序中]</li></ul><p><img src="https://mmbiz.qpic.cn/mmbiz_png/ibnDyTicARr34jWvV4icUqjVGtbKEhBicK4BfeGsBfOic3tqib246NgFVoRK0gZhZnMicPmR0KxYXEoRGALx9zoURxNYA/640?wx_fmt=png&amp;wxfrom=5&amp;wx_lazy=1&amp;wx_co=1" alt="图片"></p><p>线程上下文切换会有什么问题呢？</p><p>上下文切换会导致额外的开销，常常表现为高并发执行时速度会慢串行，因此减少上下文切换次数便可以提高多线程程序的运行效率。</p><ul><li>直接消耗：指的是CPU寄存器需要保存和加载, 系统调度器的代码需要执行, TLB实例需要重新加载, CPU 的pipeline需要刷掉</li><li>间接消耗：指的是多核的cache之间得共享数据, 间接消耗对于程序的影响要看线程工作区操作数据的大小</li></ul><h2 id="切换查看" tabindex="-1">切换查看 <a class="header-anchor" href="#切换查看" aria-label="Permalink to &quot;切换查看&quot;">​</a></h2><p>Linux系统下可以使用vmstat命令来查看上下文切换的次数， 其中cs列就是指上下文切换的数目（一般情况下, 空闲系统的上下文切换每秒大概在1500以下）</p><p><img src="https://mmbiz.qpic.cn/mmbiz_png/ibnDyTicARr34jWvV4icUqjVGtbKEhBicK4BtMduO9Micxnq21v49weUKQToB01GKdhsPS21iceb5uEn9jrhjVO1C4eg/640?wx_fmt=png&amp;wxfrom=5&amp;wx_lazy=1&amp;wx_co=1" alt="图片"></p><h2 id="线程调度" tabindex="-1">线程调度 <a class="header-anchor" href="#线程调度" aria-label="Permalink to &quot;线程调度&quot;">​</a></h2><h3 id="抢占式调度" tabindex="-1">抢占式调度 <a class="header-anchor" href="#抢占式调度" aria-label="Permalink to &quot;抢占式调度&quot;">​</a></h3><p>指的是每条线程执行的时间、线程的切换都由系统控制，系统控制指的是在系统某种运行机制下，可能每条线程都分同样的执行时间片，也可能是某些线程执行的时间片较长，甚至某些线程得不到执行的时间片。在这种机制下，一个线程的堵塞不会导致整个进程堵塞。</p><p>java使用的线程调使用抢占式调度，Java中线程会按优先级分配CPU时间片运行，且优先级越高越优先执行，但优先级高并不代表能独自占用执行时间片，可能是优先级高得到越多的执行时间片，反之，优先级低的分到的执行时间少但不会分配不到执行时间。</p><p><img src="https://mmbiz.qpic.cn/mmbiz_png/ibnDyTicARr34jWvV4icUqjVGtbKEhBicK4B8Z4iczpReibsqSgqyOBYTOvbxl4tDRnntvicPmUqlEfCxku6VLeia8sMnw/640?wx_fmt=png&amp;wxfrom=5&amp;wx_lazy=1&amp;wx_co=1" alt="图片"></p><h3 id="协同式调度" tabindex="-1">协同式调度 <a class="header-anchor" href="#协同式调度" aria-label="Permalink to &quot;协同式调度&quot;">​</a></h3><p>指某一线程执行完后主动通知系统切换到另一线程上执行，这种模式就像接力赛一样，一个人跑完自己的路程就把接力棒交接给下一个人，下个人继续往下跑。线程的执行时间由线程本身控制，线程切换可以预知，不存在多线程同步问题，但它有一个致命弱点：如果一个线程编写有问题，运行到一半就一直堵塞，那么可能导致整个系统崩溃。</p><p><img src="https://mmbiz.qpic.cn/mmbiz_png/ibnDyTicARr34jWvV4icUqjVGtbKEhBicK4B6wQ8FdZEqSmCJQGGR8sibwaibCcJHyhCUhjiaKoy5Ff0Bdv4bsjTncn0A/640?wx_fmt=png&amp;wxfrom=5&amp;wx_lazy=1&amp;wx_co=1" alt="图片"></p><h3 id="线程让出cpu的情况" tabindex="-1">线程让出cpu的情况 <a class="header-anchor" href="#线程让出cpu的情况" aria-label="Permalink to &quot;线程让出cpu的情况&quot;">​</a></h3><ul><li>当前运行线程主动放弃CPU，JVM暂时放弃CPU操作（基于时间片轮转调度的JVM操作系统不会让线程永久放弃CPU，或者说放弃本次时间片的执行权），例如调用<code>yield()</code>方法。</li><li>当前运行线程因为某些原因进入阻塞状态，例如阻塞在I/O上</li><li>当前运行线程结束，即运行完<code>run()</code>方法里面的任务</li></ul><h2 id="引起线程上下文切换的因素" tabindex="-1">引起线程上下文切换的因素 <a class="header-anchor" href="#引起线程上下文切换的因素" aria-label="Permalink to &quot;引起线程上下文切换的因素&quot;">​</a></h2><h3 id="上下文切换因素" tabindex="-1">上下文切换因素 <a class="header-anchor" href="#上下文切换因素" aria-label="Permalink to &quot;上下文切换因素&quot;">​</a></h3><ul><li>当前执行任务（线程）的时间片用完之后，系统CPU正常调度下一个任务</li><li>中断处理，在中断处理中，其他程序”打断”了当前正在运行的程序。当CPU接收到中断请求时，会在正在运行的程序和发起中断请求的程序之间进行一次上下文切换。中断分为硬件中断和软件中断，软件中断包括因为IO阻塞、未抢到资源或者用户代码等原因，线程被挂起。</li><li>用户态切换，对于一些操作系统，当进行用户态切换时也会进行一次上下文切换，虽然这不是必须的。</li><li>多个任务抢占锁资源，在多任务处理中，CPU会在不同程序之间来回切换，每个程序都有相应的处理时间片，CPU在两个时间片的间隔中进行上下文切换</li></ul><h3 id="优化手段" tabindex="-1">优化手段 <a class="header-anchor" href="#优化手段" aria-label="Permalink to &quot;优化手段&quot;">​</a></h3><ul><li>无锁并发编程，多线程处理数据时，可以用一些办法来避免使用锁，如将数据的ID按照Hash取模分段，不同的线程处理不同段的数据</li><li>CAS算法，Java的Atomic包使用CAS算法来更新数据，而不需要加锁</li><li>使用最少线程</li><li>协程，单线程里实现多任务的调度，并在单线程里维持多个任务间的切换</li></ul><p>合理设置线程数目既可以最大化利用CPU，又可以减少线程切换的开销。</p><ul><li>高并发，低耗时的情况，建议少线程。</li><li>低并发，高耗时的情况：建议多线程。</li><li>高并发高耗时，要分析任务类型、增加排队、加大线程数</li></ul><h1 id="操作系统高频面试题" tabindex="-1">操作系统高频面试题 <a class="header-anchor" href="#操作系统高频面试题" aria-label="Permalink to &quot;操作系统高频面试题&quot;">​</a></h1><h2 id="操作系统的四个特性" tabindex="-1">操作系统的四个特性？ <a class="header-anchor" href="#操作系统的四个特性" aria-label="Permalink to &quot;操作系统的四个特性？&quot;">​</a></h2><p>并发：同一段时间内多个程序执行（与并行区分，并行指的是同一时刻有多个事件，多处理器系统可以使程序并行执行）</p><p>共享：系统中的资源可以被内存中多个并发执行的进线程共同使用</p><p>虚拟：通过分时复用（如分时系统）以及空分复用（如虚拟内存）技术把一个物理实体虚拟为多个</p><p>异步：系统进程用一种走走停停的方式执行，（并不是一下子走完），进程什么时候以怎样的速度向前推进是不可预知的</p><h2 id="进程线程" tabindex="-1">进程线程 <a class="header-anchor" href="#进程线程" aria-label="Permalink to &quot;进程线程&quot;">​</a></h2><p>进程是指一个内存中运行的应用程序，每个进程都有自己独立的一块内存空间。</p><p>线程是比进程更小的执行单位，它是在一个进程中独立的控制流，一个进程可以启动多个线程，每条线程并行执行不同的任务。</p><p><strong>进程和线程的区别如下</strong>：</p><ul><li>调度：进程是资源管理的基本单位，线程是程序执行的基本单位。</li><li>切换：线程上下文切换比进程上下文切换要快得多。</li><li>拥有资源：进程是拥有资源的一个独立单位，线程不拥有系统资源，但是可以访问隶属于进程的资源。</li><li>系统开销：创建或撤销进程时，系统都要为之分配或回收系统资源，如内存空间，I/O设备等，OS所付出的开销显著大于在创建或撤销线程时的开销，进程切换的开销也远大于线程切换的开销。</li></ul><h2 id="并发和并行" tabindex="-1">并发和并行 <a class="header-anchor" href="#并发和并行" aria-label="Permalink to &quot;并发和并行&quot;">​</a></h2><p>并发就是在一段时间内，多个任务都会被处理；但在某一时刻，只有一个任务在执行。单核处理器可以做到并发。比如有两个进程<code>A</code>和<code>B</code>，<code>A</code>运行一个时间片之后，切换到<code>B</code>，<code>B</code>运行一个时间片之后又切换到<code>A</code>。因为切换速度足够快，所以宏观上表现为在一段时间内能同时运行多个程序。</p><p>并行就是在同一时刻，有多个任务在执行。这个需要多核处理器才能完成，在微观上就能同时执行多条指令，不同的程序被放到不同的处理器上运行，这个是物理上的多个进程同时进行。</p><h2 id="多线程相较单线程的好处" tabindex="-1">多线程相较单线程的好处 <a class="header-anchor" href="#多线程相较单线程的好处" aria-label="Permalink to &quot;多线程相较单线程的好处&quot;">​</a></h2><p>1、并发提升程序执行效率</p><p>2、提升CPU利用率，访存的时候可以切换线程来执行</p><p>3、更快的响应速度，可以有专门的线程来监听用户请求和专门的线程来处理请求。比如监听线程和工作线程是两个线程，这样监听就负责监听，工作的就负责工作，监听到用户请求马上把请求转到工作线程去处理，监听线程继续监听</p><h2 id="什么是协程" tabindex="-1">什么是协程？ <a class="header-anchor" href="#什么是协程" aria-label="Permalink to &quot;什么是协程？&quot;">​</a></h2><p>协程是一种用户态的轻量级线程。</p><p>协程不是由操作系统内核管理，而是完全由用户程序所控制，这样带来的好处就是性能得到了很大的提升，不会像线程切换那样消耗资源。</p><p>协程可以理解为可以暂停执行的函数。它拥有自己的寄存器上下文和栈。协程调度切换时，将寄存器上下文和栈保存到其他地方，在切回来的时候，恢复先前保存的寄存器上下文和栈，直接操作栈则基本没有内核切换的开销，可以不加锁的访问全局变量，所以上下文的切换非常快。</p><h2 id="线程和协程有什么区别呢" tabindex="-1">线程和协程有什么区别呢？ <a class="header-anchor" href="#线程和协程有什么区别呢" aria-label="Permalink to &quot;线程和协程有什么区别呢？&quot;">​</a></h2><p>1、线程是抢占式，而协程是非抢占式的，所以需要用户自己释放使用权来切换到其他协程，因此同一时间其实只有一个协程拥有运行权，相当于单线程的能力。2、线程是协程的资源。协程通过 可以关联任意线程或线程池的执行器（Interceptor）来间接使用线程的资源的。</p><h2 id="进程通信" tabindex="-1">进程通信 <a class="header-anchor" href="#进程通信" aria-label="Permalink to &quot;进程通信&quot;">​</a></h2><p>进程间通信方式有以下几种：</p><p>1、<strong>管道通信</strong></p><p>匿名管道( pipe )：管道是一种半双工的通信方式，数据只能单向流动，而且只能在具有亲缘关系的进程间使用。进程的亲缘关系通常是指父子进程关系。有名管道是半双工的通信方式，数据只能单向流动。</p><p>2、<strong>消息队列</strong></p><p>3、<strong>共享内存</strong>。共享内存是最快的 IPC 方式，它是针对其他进程间通信方式运行效率低而专门设计的。它往往与其他通信机制，如信号量，配合使用，来实现进程间的同步和通信。</p><p>4、<strong>信号量</strong>。信号量是一个计数器，可以用来控制多个进程对共享资源的访问。它常作为一种锁机制，防止某进程正在访问共享资源时，其他进程也访问该资源。因此，主要作为进程间以及同一进程内不同线程之间的同步手段。</p><h2 id="什么是死锁" tabindex="-1">什么是死锁？ <a class="header-anchor" href="#什么是死锁" aria-label="Permalink to &quot;什么是死锁？&quot;">​</a></h2><p>死锁是指两个或两个以上的线程在执行过程中，因争夺资源而造成的一种互相等待的现象。若无外力作用，它们都将无法推进下去。</p><p>如下图所示，线程 A 持有资源 2，线程 B 持有资源 1，他们同时都想申请对方持有的资源，所以这两个线程就会互相等待而进入死锁状态。</p><p><img src="https://mmbiz.qpic.cn/mmbiz_png/1Wxib6Z0MOJawZSWSpibHu2SSXucsMOgeqMJKQqmkiazA98eP6Frdibb6x1LzHicVhic5BUJjWQM6bN7eqWDicMTacOgg/640?wx_fmt=png&amp;wxfrom=5&amp;wx_lazy=1&amp;wx_co=1" alt="图片"></p><p>下面通过例子说明线程死锁，代码来自并发编程之美。</p><div class="language-"><button title="Copy Code" class="copy"></button><span class="lang"></span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#A6ACCD;">public class DeadLockDemo {</span></span>
<span class="line"><span style="color:#A6ACCD;">    private static Object resource1 = new Object();//资源 1</span></span>
<span class="line"><span style="color:#A6ACCD;">    private static Object resource2 = new Object();//资源 2</span></span>
<span class="line"><span style="color:#A6ACCD;"></span></span>
<span class="line"><span style="color:#A6ACCD;">    public static void main(String[] args) {</span></span>
<span class="line"><span style="color:#A6ACCD;">        new Thread(() -&gt; {</span></span>
<span class="line"><span style="color:#A6ACCD;">            synchronized (resource1) {</span></span>
<span class="line"><span style="color:#A6ACCD;">                System.out.println(Thread.currentThread() + &quot;get resource1&quot;);</span></span>
<span class="line"><span style="color:#A6ACCD;">                try {</span></span>
<span class="line"><span style="color:#A6ACCD;">                    Thread.sleep(1000);</span></span>
<span class="line"><span style="color:#A6ACCD;">                } catch (InterruptedException e) {</span></span>
<span class="line"><span style="color:#A6ACCD;">                    e.printStackTrace();</span></span>
<span class="line"><span style="color:#A6ACCD;">                }</span></span>
<span class="line"><span style="color:#A6ACCD;">                System.out.println(Thread.currentThread() + &quot;waiting get resource2&quot;);</span></span>
<span class="line"><span style="color:#A6ACCD;">                synchronized (resource2) {</span></span>
<span class="line"><span style="color:#A6ACCD;">                    System.out.println(Thread.currentThread() + &quot;get resource2&quot;);</span></span>
<span class="line"><span style="color:#A6ACCD;">                }</span></span>
<span class="line"><span style="color:#A6ACCD;">            }</span></span>
<span class="line"><span style="color:#A6ACCD;">        }, &quot;线程 1&quot;).start();</span></span>
<span class="line"><span style="color:#A6ACCD;"></span></span>
<span class="line"><span style="color:#A6ACCD;">        new Thread(() -&gt; {</span></span>
<span class="line"><span style="color:#A6ACCD;">            synchronized (resource2) {</span></span>
<span class="line"><span style="color:#A6ACCD;">                System.out.println(Thread.currentThread() + &quot;get resource2&quot;);</span></span>
<span class="line"><span style="color:#A6ACCD;">                try {</span></span>
<span class="line"><span style="color:#A6ACCD;">                    Thread.sleep(1000);</span></span>
<span class="line"><span style="color:#A6ACCD;">                } catch (InterruptedException e) {</span></span>
<span class="line"><span style="color:#A6ACCD;">                    e.printStackTrace();</span></span>
<span class="line"><span style="color:#A6ACCD;">                }</span></span>
<span class="line"><span style="color:#A6ACCD;">                System.out.println(Thread.currentThread() + &quot;waiting get resource1&quot;);</span></span>
<span class="line"><span style="color:#A6ACCD;">                synchronized (resource1) {</span></span>
<span class="line"><span style="color:#A6ACCD;">                    System.out.println(Thread.currentThread() + &quot;get resource1&quot;);</span></span>
<span class="line"><span style="color:#A6ACCD;">                }</span></span>
<span class="line"><span style="color:#A6ACCD;">            }</span></span>
<span class="line"><span style="color:#A6ACCD;">        }, &quot;线程 2&quot;).start();</span></span>
<span class="line"><span style="color:#A6ACCD;">    }</span></span>
<span class="line"><span style="color:#A6ACCD;">}</span></span></code></pre></div><p>代码输出如下：</p><div class="language-"><button title="Copy Code" class="copy"></button><span class="lang"></span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#A6ACCD;">Thread[线程 1,5,main]get resource1</span></span>
<span class="line"><span style="color:#A6ACCD;">Thread[线程 2,5,main]get resource2</span></span>
<span class="line"><span style="color:#A6ACCD;">Thread[线程 1,5,main]waiting get resource2</span></span>
<span class="line"><span style="color:#A6ACCD;">Thread[线程 2,5,main]waiting get resource1</span></span></code></pre></div><p>线程 A 通过 <code>synchronized</code> (resource1) 获得 resource1 的监视器锁，然后通过 <code>Thread.sleep(1000)</code>。让线程 A 休眠 1s 为的是让线程 B 得到执行然后获取到 resource2 的监视器锁。线程 A 和线程 B 休眠结束了都开始企图请求获取对方的资源，然后这两个线程就会陷入互相等待的状态，这也就产生了死锁。</p><h2 id="死锁怎么产生-怎么避免" tabindex="-1">死锁怎么产生？怎么避免？ <a class="header-anchor" href="#死锁怎么产生-怎么避免" aria-label="Permalink to &quot;死锁怎么产生？怎么避免？&quot;">​</a></h2><p><strong>死锁产生的四个必要条件</strong>：</p><ul><li>互斥：一个资源每次只能被一个进程使用</li><li>请求与保持：一个进程因请求资源而阻塞时，不释放获得的资源</li><li>不剥夺：进程已获得的资源，在未使用之前，不能强行剥夺</li><li>循环等待：进程之间循环等待着资源</li></ul><p><strong>避免死锁的方法</strong>：</p><ul><li>互斥条件不能破坏，因为加锁就是为了保证互斥</li><li>一次性申请所有的资源，避免线程占有资源而且在等待其他资源</li><li>占有部分资源的线程进一步申请其他资源时，如果申请不到，主动释放它占有的资源</li><li>按序申请资源</li></ul><h2 id="进程调度策略有哪几种" tabindex="-1">进程调度策略有哪几种？ <a class="header-anchor" href="#进程调度策略有哪几种" aria-label="Permalink to &quot;进程调度策略有哪几种？&quot;">​</a></h2><ul><li><p><strong>先来先服务</strong>：非抢占式的调度算法，按照请求的顺序进行调度。有利于长作业，但不利于短作业，因为短作业必须一直等待前面的长作业执行完毕才能执行，而长作业又需要执行很长时间，造成了短作业等待时间过长。另外，对<code>I/O</code>密集型进程也不利，因为这种进程每次进行<code>I/O</code>操作之后又得重新排队。</p></li><li><p><strong>短作业优先</strong>：非抢占式的调度算法，按估计运行时间最短的顺序进行调度。长作业有可能会饿死，处于一直等待短作业执行完毕的状态。因为如果一直有短作业到来，那么长作业永远得不到调度。</p></li><li><p><strong>最短剩余时间优先</strong>：最短作业优先的抢占式版本，按剩余运行时间的顺序进行调度。当一个新的作业到达时，其整个运行时间与当前进程的剩余时间作比较。如果新的进程需要的时间更少，则挂起当前进程，运行新的进程。否则新的进程等待。</p></li><li><p><strong>时间片轮转</strong>：将所有就绪进程按 <code>FCFS</code> 的原则排成一个队列，每次调度时，把 <code>CPU</code> 时间分配给队首进程，该进程可以执行一个时间片。当时间片用完时，由计时器发出时钟中断，调度程序便停止该进程的执行，并将它送往就绪队列的末尾，同时继续把 <code>CPU</code> 时间分配给队首的进程。</p><p>时间片轮转算法的效率和时间片的大小有很大关系：因为进程切换都要保存进程的信息并且载入新进程的信息，如果时间片太小，会导致进程切换得太频繁，在进程切换上就会花过多时间。而如果时间片过长，那么实时性就不能得到保证。</p></li><li><p><strong>优先级调度</strong>：为每个进程分配一个优先级，按优先级进行调度。为了防止低优先级的进程永远等不到调度，可以随着时间的推移增加等待进程的优先级。</p></li></ul><h2 id="进程有哪些状态" tabindex="-1">进程有哪些状态？ <a class="header-anchor" href="#进程有哪些状态" aria-label="Permalink to &quot;进程有哪些状态？&quot;">​</a></h2><p>进程一共有<code>5</code>种状态，分别是创建、就绪、运行（执行）、终止、阻塞。</p><p><img src="https://mmbiz.qpic.cn/mmbiz_png/1Wxib6Z0MOJawZSWSpibHu2SSXucsMOgeqwMTchRUoSM1nXn1auMVXS5iafCYROcibQVRG8W8wcdicPhBWw3wuIkx7A/640?wx_fmt=png&amp;wxfrom=5&amp;wx_lazy=1&amp;wx_co=1" alt="图片"></p><ul><li>运行状态就是进程正在<code>CPU</code>上运行。在单处理机环境下，每一时刻最多只有一个进程处于运行状态。</li><li>就绪状态就是说进程已处于准备运行的状态，即进程获得了除<code>CPU</code>之外的一切所需资源，一旦得到<code>CPU</code>即可运行。</li><li>阻塞状态就是进程正在等待某一事件而暂停运行，比如等待某资源为可用或等待<code>I/O</code>完成。即使<code>CPU</code>空闲，该进程也不能运行。</li></ul><p><strong>运行态→阻塞态</strong>：往往是由于等待外设，等待主存等资源分配或等待人工干预而引起的。</p><p><strong>阻塞态→就绪态</strong>：则是等待的条件已满足，只需分配到处理器后就能运行。</p><p><strong>运行态→就绪态</strong>：不是由于自身原因，而是由外界原因使运行状态的进程让出处理器，这时候就变成就绪态。例如时间片用完，或有更高优先级的进程来抢占处理器等。</p><p><strong>就绪态→运行态</strong>：系统按某种策略选中就绪队列中的一个进程占用处理器，此时就变成了运行态。</p><h2 id="操作系统里的内存碎片怎么理解" tabindex="-1">操作系统里的内存碎片怎么理解？ <a class="header-anchor" href="#操作系统里的内存碎片怎么理解" aria-label="Permalink to &quot;操作系统里的内存碎片怎么理解？&quot;">​</a></h2><p>内存碎片通常分为内部碎片和外部碎片：</p><ol><li>内部碎片是由于采用固定大小的内存分区，当一个进程不能完全使用分给它的固定内存区域时就会产生内部碎片。通常内部碎片难以完全避免</li><li>外部碎片是由于某些未分配的连续内存区域太小，以至于不能满足任意进程的内存分配请求，从而不能被进程利用的内存区域。</li></ol><p><strong>有什么解决办法</strong>？</p><p>现在普遍采取的内存分配方式是段页式内存分配。将内存分为不同的段，再将每一段分成固定大小的页。通过页表机制，使段内的页可以不必连续处于同一内存区域。</p><h2 id="虚拟内存-1" tabindex="-1">虚拟内存 <a class="header-anchor" href="#虚拟内存-1" aria-label="Permalink to &quot;虚拟内存&quot;">​</a></h2><p>虚拟存储器就是具有请求调入功能，能从逻辑上对内存容量加以扩充的一种存储器系统，虚拟内存有多次性，对换性和虚拟性三个特征，它可以将程序分多次调入内存，使得在较小的用户空间可以执行较大的用户程序，所以同时容纳更多的进程并发执行，从而提高系统的吞吐量。发生缺页时可以调入一个段也可以调入一个页，取决于内存的存储管理方式。虚拟性表示虚拟内存和物理内存的映射。</p><p>Linux下，进程不能直接读写内存物理地址，只能访问【虚拟内存地址】。操作系统会把虚拟内存地址--&gt;物理地址。</p><p>虚拟内存解决有限的内存空间加载较大应用程序的问题，根据需要在内存和磁盘之间来回传送数据。</p><p>通过段页表的形式，虚拟内存中取一段连续的内存空间映射到主内存中，主内存空间的程序段可以不连续 。</p><h2 id="什么是分页" tabindex="-1">什么是分页？ <a class="header-anchor" href="#什么是分页" aria-label="Permalink to &quot;什么是分页？&quot;">​</a></h2><p>把内存空间划分为<strong>大小相等且固定的块</strong>，作为主存的基本单位。因为程序数据存储在不同的页面中，而页面又离散的分布在内存中，<strong>因此需要一个页表来记录映射关系，以实现从页号到物理块号的映射。</strong></p><p>访问分页系统中内存数据需要<strong>两次的内存访问</strong> (一次是从内存中访问页表，从中找到指定的物理块号，加上页内偏移得到实际物理地址；第二次就是根据第一次得到的物理地址访问内存取出数据)。</p><h2 id="什么是分段" tabindex="-1">什么是分段？ <a class="header-anchor" href="#什么是分段" aria-label="Permalink to &quot;什么是分段？&quot;">​</a></h2><p><strong>分页是为了提高内存利用率，而分段是为了满足程序员在编写代码的时候的一些逻辑需求(比如数据共享，数据保护，动态链接等)。</strong></p><p>分段内存管理当中，<strong>地址是二维的，一维是段号，二维是段内地址；其中每个段的长度是不一样的，而且每个段内部都是从0开始编址的</strong>。由于分段管理中，每个段内部是连续内存分配，但是段和段之间是离散分配的，因此也存在一个逻辑地址到物理地址的映射关系，相应的就是段表机制。</p><h2 id="分页和分段有什区别" tabindex="-1">分页和分段有什区别？ <a class="header-anchor" href="#分页和分段有什区别" aria-label="Permalink to &quot;分页和分段有什区别？&quot;">​</a></h2><ul><li>分页对程序员是透明的，但是分段需要程序员显式划分每个段。</li><li>分页的地址空间是一维地址空间，分段是二维的。</li><li>页的大小不可变，段的大小可以动态改变。</li><li>分页主要用于实现虚拟内存，从而获得更大的地址空间；分段主要是为了使程序和数据可以被划分为逻辑上独立的地址空间并且有助于共享和保护。</li></ul><h2 id="页面置换算法" tabindex="-1">页面置换算法 <a class="header-anchor" href="#页面置换算法" aria-label="Permalink to &quot;页面置换算法&quot;">​</a></h2><p><strong>为什么要页面置换：</strong></p><p>因为应用程序是分多次装入内存的，所以运行到一定的时间，一定会发生缺页。地址映射的过程中，如果页面中发现要访问的页面不在内存中，会产生缺页中断。此时操作系统必须在内存里选择一个页面把他移出内存，为即将调入的页面让出空间。选择淘汰哪一页的规则就是页面置换算法</p><p><strong>几种页面置换算法：</strong></p><p><strong>最佳置换算法（理想）</strong>：将当前页面中在未来最长时间内不会被访问的页置换出去</p><p><strong>先进先出</strong>：淘汰最早调入的页面</p><p><strong>最近最久未使用 LRU</strong>：每个页面有一个t来记录上次页面被访问直到现在，每次置换时置换t值最大的页面（用寄存器或栈实现）</p><p><strong>时钟算法clock</strong>（也被称为最近未使用算法NRU）：页面设置访问为，将页面链接为一个环形列表，每个页有一个访问位0/1, 1表示又一次获救的机会，下次循环指针指向它时可以免除此次置换，但是会把访问位置为0， 代表他下次如果碰到循环指针就该被置换了。页面被访问的时候访问位设为1。页面置换的时候，如果当前指针的访问位为0，置换，否则将这个值置为0，循环直到遇到访问位为0的页面。</p><p><strong>改进型Clock算法</strong>：在clock算法的基础上添加一个修改位，优先替换访问位和修改位都是0的页面，其次替换访问位为0修改位为1的页面。</p><p><strong>最少使用算法LFU</strong>：设置寄存器记录页面被访问次数，每次置换当前访问次数最少的。</p><h2 id="用户态和内核态" tabindex="-1">用户态和内核态 <a class="header-anchor" href="#用户态和内核态" aria-label="Permalink to &quot;用户态和内核态&quot;">​</a></h2><p>内核态：cpu可以访问内存的所有数据，包括外围设备，例如硬盘，网卡，cpu也可以将自己从一个程序切换到另一个程序。</p><p>用户态：只能受限的访问内存，且不允许访问外围设备，占用cpu的能力被剥夺，cpu资源可以被其他程序获取。</p><p>最大的区别就是权限不同，在运行在用户态下的程序不能直接访问操作系统内核数据结构和程序。</p><h3 id="为什么要有这两种状态" tabindex="-1">为什么要有这两种状态？ <a class="header-anchor" href="#为什么要有这两种状态" aria-label="Permalink to &quot;为什么要有这两种状态？&quot;">​</a></h3><p>内核速度快但是资源有限，能控制的进程数不多，所以需要速度慢一些的用户态协助，但是为了避免用户态被恶意利用，所以限制了用户态程序的权限。</p><p>需要限制不同的程序之间的访问能力，防止他们获取别的程序的内存数据，或者获取外围设备的数据，并发送到网络，CPU划分出<strong>两个权限等级</strong> -- 用户态和内核态。</p><h3 id="什么时候转换" tabindex="-1">什么时候转换 <a class="header-anchor" href="#什么时候转换" aria-label="Permalink to &quot;什么时候转换&quot;">​</a></h3><p><strong>1、系统调用</strong>：</p><p>用户进程主动发起的。用户态进程通过系统调用申请使用操作系统提供的服务程序完成工作，比如fork()就是执行一个创建新进程的系统调用</p><p>用户程序使用系统调用，系统调用会转换为内核态并调用操作系统</p><p><strong>2、发生异常</strong>：</p><p>会从当前运行进程切换到处理次此异常的内核相关程序中</p><p><strong>3、外围设备的中断：</strong></p><p>所有程序都运行在用户态，但在从硬盘读取数据、或从键盘输入时，这些事情只有操作系统能做，程序需要向操作系统请求以程序的名义来执行这些操作。这个时候用户态程序切换到内核态。</p><h2 id="什么是缓冲区溢出-有什么危害" tabindex="-1">什么是缓冲区溢出？有什么危害？ <a class="header-anchor" href="#什么是缓冲区溢出-有什么危害" aria-label="Permalink to &quot;什么是缓冲区溢出？有什么危害？&quot;">​</a></h2><p>缓冲区溢出是指当计算机向缓冲区填充数据时超出了缓冲区本身的容量，溢出的数据覆盖在合法数据上。</p><p>危害有以下两点：</p><ul><li>程序崩溃，导致拒绝额服务</li><li>跳转并且执行一段恶意代码</li></ul><p>造成缓冲区溢出的主要原因是程序中没有仔细检查用户输入。</p><h2 id="io多路复用" tabindex="-1">IO多路复用 <a class="header-anchor" href="#io多路复用" aria-label="Permalink to &quot;IO多路复用&quot;">​</a></h2><p><strong>IO多路复用是指内核一旦发现进程指定的一个或者多个IO条件准备读取，它就通知该进程。IO多路复用适用如下场合</strong>：</p><ul><li>当客户处理多个描述字时（一般是交互式输入和网络套接口），必须使用I/O复用。</li><li>当一个客户同时处理多个套接口时，而这种情况是可能的，但很少出现。</li><li>如果一个TCP服务器既要处理监听套接口，又要处理已连接套接口，一般也要用到I/O复用。</li><li>如果一个服务器即要处理TCP，又要处理UDP，一般要使用I/O复用。</li><li>如果一个服务器要处理多个服务或多个协议，一般要使用I/O复用。</li><li>与多进程和多线程技术相比，I/O多路复用技术的最大优势是系统开销小，系统不必创建进程/线程，也不必维护这些进程/线程，从而大大减小了系统的开销。</li></ul></div></div></main><footer class="VPDocFooter" data-v-6b87e69f data-v-37656e44><!--[--><!--]--><!----><nav class="prev-next" data-v-37656e44><div class="pager" data-v-37656e44><a class="pager-link prev" href="/notebook/%E8%AE%A1%E7%AE%97%E6%9C%BA%E5%9F%BA%E7%A1%80/%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84/%E5%9F%BA%E7%A1%80%E7%AF%87.html" data-v-37656e44><span class="desc" data-v-37656e44>Previous page</span><span class="title" data-v-37656e44>数据结构</span></a></div><div class="pager" data-v-37656e44><a class="pager-link next" href="/notebook/%E8%AE%A1%E7%AE%97%E6%9C%BA%E5%9F%BA%E7%A1%80/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F/%E5%9F%BA%E7%A1%80%E7%AF%87.html" data-v-37656e44><span class="desc" data-v-37656e44>Next page</span><span class="title" data-v-37656e44>设计模式</span></a></div></nav></footer><!--[--><!--]--></div></div></div><!--[--><!--]--></div></div><!----><!--[--><!--]--></div></div>
    <script>window.__VP_HASH_MAP__=JSON.parse("{\"2、数据库_mysql_mysql面试_基础.md\":\"40da680a\",\"1、学前端_5、小程序_小程序项目.md\":\"60a1629b\",\"1、学前端_4、node_知识篇.md\":\"a7fb500e\",\"1、学前端_2、js_ts_es6 进阶.md\":\"6d07ba10\",\"1、学前端_3、vue_vue3_vue3进阶.md\":\"7ac622b4\",\"5、运维_jenkins.md\":\"929081f8\",\"1、学前端_2、js_ts_typescript.md\":\"875a4aa4\",\"2、数据库_mysql_mysql核心_设计.md\":\"7faf46d1\",\"2、数据库_mysql_mysql核心_基础.md\":\"d8e97f3e\",\"1、学前端_1、html_css_html基础.md\":\"7584d076\",\"1、学前端_5、专题篇_问题篇.md\":\"e893aaa2\",\"2、数据库_mysql_mysql面试_进阶.md\":\"f934806d\",\"3、springboot_运维_原理.md\":\"f4a39db6\",\"2、数据库_influxdb.md\":\"6e1711e1\",\"3、springboot_新特性.md\":\"cdf3e307\",\"mybatis_mybatisplus_jpa.md\":\"8e41681b\",\"1、学前端_5、小程序_小程序优化.md\":\"a2185198\",\"2、数据库_redis_redis基础.md\":\"856df0e0\",\"linux_实用脚本.md\":\"f2299dd5\",\"4、微服务_必备_分布式基础.md\":\"d49863d5\",\"2、数据库_redis_redis优化.md\":\"e66ae32f\",\"4、微服务_springsecurity_进阶篇.md\":\"235a8e9e\",\"5、运维_chatgpt.md\":\"10db3823\",\"2、数据库_mysql_分库分表.md\":\"e1c8a095\",\"start.md\":\"9bc1ff8d\",\"5、运维_github.md\":\"2ec6c735\",\"java学前端_css.md\":\"f11b47f0\",\"1、学前端_5、专题篇_知识篇.md\":\"a463ed8d\",\"linux_软件部署.md\":\"d6722925\",\"2、数据库_neo4j.md\":\"97ad22ac\",\"team.md\":\"ce467a6a\",\"nginx_实战篇.md\":\"7785486e\",\"index.md\":\"8c3ec167\",\"计算机基础_计算机网络_网络基础.md\":\"7a54a85d\",\"1、学前端_4、node_进阶篇.md\":\"60f6db69\",\"java_java集合.md\":\"a049b313\",\"1、学前端_3、vue_vue3_vue3高级.md\":\"614d1516\",\"1、学前端_5、小程序_微信小程序.md\":\"9a4be771\",\"5、运维_netty.md\":\"12ca0278\",\"2、数据库_mysql_mysql核心_运维.md\":\"83f97c16\",\"idea_vs code.md\":\"afdcb593\",\"java学前端_vue3_组件.md\":\"1086884e\",\"idea_chrome.md\":\"4a32afbc\",\"云原生_k8s.md\":\"db58e65a\",\"2、数据库_mysql_mysql核心_进阶.md\":\"61d16dff\",\"ssm_springbatch.md\":\"f799ab4a\",\"三高_分布式.md\":\"db1b8a1b\",\"2、数据库_elasticsearch_1、es基础.md\":\"04d17448\",\"linux_linux基础.md\":\"4b0bf394\",\"idea_idea插件.md\":\"fa86e45a\",\"可视化 _ 监控_可视化大屏.md\":\"004553bd\",\"2、数据库_mongodb_整合.md\":\"3c47d7f4\",\"4、微服务_springsecurity_基础篇.md\":\"534a3401\",\"4、微服务_进阶.md\":\"69095c58\",\"计算机基础_计算机基础_操作系统.md\":\"0f75d113\",\"可视化 _ 监控_zabbix.md\":\"71f2270e\",\"nginx_基础篇.md\":\"c7d8bb50\",\"1、学前端_4、node_项目实战.md\":\"bc5065b8\",\"2、数据库_redis_redis原理.md\":\"5cedf685\",\"可视化 _ 监控_监控基础.md\":\"ac56ce4d\",\"三高_高并发.md\":\"ea9ffc99\",\"2、数据库_redis_redis高级.md\":\"1d5872f6\",\"1、学前端_4、node_基础篇.md\":\"581cc13a\",\"2、数据库_mongodb_基础.md\":\"fb7a0a29\",\"idea_idea基础.md\":\"6f2f9638\",\"4、微服务_必备_sentinel.md\":\"2edfbf6c\",\"2、数据库_elasticsearch_3、es高级.md\":\"ef146606\",\"1、学前端_3、vue_vue3_vue3新语法.md\":\"8afd5409\",\"消息中间件_canal.md\":\"3949163c\",\"ssm_maven.md\":\"2c5e12ed\",\"4、微服务_springsecurity_高级篇.md\":\"882d3ff3\",\"linux_linux进阶.md\":\"188ef7b4\",\"计算机基础_设计模式_uml.md\":\"634ba256\",\"计算机基础_算法_leetcode.md\":\"77162fb9\",\"项目实战_小兔鲜_进阶篇1.md\":\"17c52c81\",\"1、学前端_2、js_ts_es6 基础.md\":\"fda3f18b\",\"项目实战_小兔鲜_进阶篇2.md\":\"a0f23006\",\"软件测试_测试基础.md\":\"8c1060cd\",\"2、数据库_redis_本地缓存.md\":\"00617fe6\",\"nginx_面试篇.md\":\"e3fb373a\",\"mybatis_mybatisplus_mybatis.md\":\"9239e0ad\",\"linux_shell.md\":\"ae53d83b\",\"2、数据库_mysql_mysql核心_优化.md\":\"36230425\",\"项目实战_项目推荐.md\":\"f9d97630\",\"mybatis_mybatisplus_mybatisplus.md\":\"0030fd35\",\"项目实战_百度地图_进阶篇.md\":\"c8b93267\",\"三高_高可用.md\":\"323840c5\",\"java_java新特性.md\":\"22abf56d\",\"软件测试_压力测试.md\":\"9ab44440\",\"java学前端_html_js.md\":\"e0fcd240\",\"2、数据库_redis_redis实战.md\":\"d6daeeab\",\"nginx_进阶篇.md\":\"e6b63195\",\"三高_秒杀.md\":\"3878bb64\",\"5、运维_git.md\":\"0264925c\",\"java_java进阶.md\":\"e79cb5b4\",\"并发 _ 多线程_基础篇.md\":\"7adbfac5\",\"项目实战_百度地图_基础篇.md\":\"8afa5954\",\"java学前端_react.md\":\"3ec827dd\",\"1、学前端_1、html_css_css基础.md\":\"01b56712\",\"项目实战_小兔鲜_基础篇.md\":\"646f5df5\",\"1、学前端_2、js_ts_js 基础.md\":\"cb13e36f\",\"可视化 _ 监控_监控进阶.md\":\"0cdbc292\",\"计算机基础_设计模式_基础篇.md\":\"51617287\",\"计算机基础_数据结构_基础篇.md\":\"b2bfd8d4\",\"项目实战_苍穹外卖_进阶篇.md\":\"48415e41\",\"ssm_spring.md\":\"ab514659\",\"消息中间件_rabbitmq.md\":\"45b1eb28\",\"1、学前端_1、html_css_网页进阶.md\":\"db998248\",\"消息中间件_kafka.md\":\"b747dabf\",\"云原生_docker.md\":\"983c7ba7\",\"4、微服务_必备_分布式锁.md\":\"5af1cf8d\",\"消息中间件_rocketmq.md\":\"d441da85\",\"项目实战_黑马头条_基础篇.md\":\"b05af3a6\",\"ssm_springmvc.md\":\"81b9714f\",\"项目实战_支付.md\":\"1d7407dd\",\"项目实战_黑马头条_进阶篇2.md\":\"bff0015b\",\"项目实战_黑马头条_进阶篇.md\":\"19f18388\",\"java学前端_vue2_组件.md\":\"58c6b1df\",\"3、springboot_基础篇.md\":\"529c66f4\",\"3、springboot_应用篇.md\":\"8b92aa61\",\"项目实战_黑马头条_高级篇.md\":\"227c08c1\",\"1、学前端_5、小程序_uniapp.md\":\"71a282b4\",\"项目实战_云尚办公_基础篇.md\":\"1fe188ba\",\"并发 _ 多线程_并发完善.md\":\"26619c46\",\"1、学前端_2、js_ts_js 进阶.md\":\"657dfb8f\",\"java_java高级.md\":\"23782d1a\",\"java_java基础.md\":\"86d67c77\"}");window.__VP_SITE_DATA__=JSON.parse("{\"lang\":\"en-US\",\"dir\":\"ltr\",\"title\":\"VitePress\",\"description\":\"A VitePress site\",\"base\":\"/notebook/\",\"head\":[],\"appearance\":true,\"themeConfig\":{\"algolia\":{\"appId\":\"DW7O63I9IR\",\"apiKey\":\"f8ed758cdb288a8b06542bc35923c1a1\",\"indexName\":\"notebook\"},\"sidebar\":[{\"text\":\"Java\",\"collapsed\":true,\"items\":[{\"text\":\"Java基础\",\"link\":\"/Java/Java基础\"},{\"text\":\"Java新特性\",\"link\":\"/Java/Java新特性\"},{\"text\":\"Java进阶\",\"link\":\"/Java/Java进阶\"},{\"text\":\"Java集合\",\"link\":\"/Java/Java集合\"},{\"text\":\"Java高级\",\"link\":\"/Java/Java高级\"}]},{\"text\":\"Linux\",\"collapsed\":true,\"items\":[{\"text\":\"Linux基础\",\"link\":\"/Linux/Linux基础\"},{\"text\":\"Linux新特性\",\"link\":\"/Linux/Linux进阶\"},{\"text\":\"Shell脚本\",\"link\":\"/Linux/Shell\"},{\"text\":\"实用脚本\",\"link\":\"/Linux/实用脚本\"},{\"text\":\"软件部署\",\"link\":\"/Linux/软件部署\"}]},{\"text\":\"Nginx\",\"collapsed\":true,\"items\":[{\"text\":\"基础篇\",\"link\":\"/Nginx/基础篇\"},{\"text\":\"进阶篇\",\"link\":\"/Nginx/进阶篇\"},{\"text\":\"实战篇\",\"link\":\"/Nginx/实战篇\"},{\"text\":\"面试篇\",\"link\":\"/Nginx/面试篇\"}]},{\"text\":\"SSM\",\"collapsed\":true,\"items\":[{\"text\":\"Maven\",\"link\":\"/SSM/Maven\"},{\"text\":\"Spring\",\"link\":\"/SSM/Spring\"},{\"text\":\"SpringMVC\",\"link\":\"/SSM/SpringMVC\"},{\"text\":\"SpringBatch\",\"link\":\"/SSM/SpringBatch\"}]},{\"text\":\"SpringBoot\",\"collapsed\":true,\"items\":[{\"text\":\"基础篇\",\"link\":\"/3、SpringBoot/基础篇\"},{\"text\":\"应用篇\",\"link\":\"/3、SpringBoot/应用篇\"},{\"text\":\"新特性\",\"link\":\"/3、SpringBoot/新特性\"},{\"text\":\"运维&原理\",\"link\":\"/3、SpringBoot/运维&原理\"}]},{\"text\":\"SpringCloud\",\"collapsed\":true,\"items\":[{\"text\":\"SpringCloud\",\"link\":\"/4、微服务/进阶\"},{\"text\":\"Sentinel\",\"link\":\"/4、微服务/必备/Sentinel\"}]},{\"text\":\"SpringSecurity\",\"collapsed\":true,\"items\":[{\"text\":\"SpringSecurity基础篇\",\"link\":\"/4、微服务/SpringSecurity/基础篇\"},{\"text\":\"SpringSecurity进阶篇\",\"link\":\"/4、微服务/SpringSecurity/进阶篇\"},{\"text\":\"SpringSecurity高级篇\",\"link\":\"/4、微服务/SpringSecurity/高级篇\"}]},{\"text\":\"Mybatis & MybatisPlus\",\"collapsed\":true,\"items\":[{\"text\":\"Mybatis\",\"link\":\"/Mybatis&MybatisPlus/Mybatis\"},{\"text\":\"MybatisPlus\",\"link\":\"/Mybatis&MybatisPlus/MybatisPlus\"},{\"text\":\"JPA\",\"link\":\"/Mybatis&MybatisPlus/JPA\"}]},{\"text\":\"Git & ChatGPT\",\"collapsed\":true,\"items\":[{\"text\":\"Git\",\"link\":\"/5、运维/Git\"},{\"text\":\"Github\",\"link\":\"/5、运维/Github\"},{\"text\":\"ChatGPT\",\"link\":\"/5、运维/ChatGPT\"},{\"text\":\"Jenkins\",\"link\":\"/5、运维/Jenkins\"},{\"text\":\"Netty\",\"link\":\"/5、运维/Netty\"}]},{\"text\":\"数据库\",\"collapsed\":true,\"items\":[{\"text\":\"MySQL\",\"collapsed\":true,\"items\":[{\"text\":\"MySQL基础\",\"link\":\"/2、数据库/MySQL/MySQL核心/基础\"},{\"text\":\"MySQL进阶\",\"link\":\"/2、数据库/MySQL/MySQL核心/进阶\"},{\"text\":\"MySQL优化\",\"link\":\"/2、数据库/MySQL/MySQL核心/优化\"},{\"text\":\"MySQL设计\",\"link\":\"/2、数据库/MySQL/MySQL核心/设计\"},{\"text\":\"MySQL运维\",\"link\":\"/2、数据库/MySQL/MySQL核心/运维\"},{\"text\":\"分库分表\",\"link\":\"/2、数据库/MySQL/分库分表\"}]},{\"text\":\"Redis\",\"collapsed\":true,\"items\":[{\"text\":\"Redis基础\",\"link\":\"/2、数据库/Redis/Redis基础\"},{\"text\":\"Redis优化\",\"link\":\"/2、数据库/Redis/Redis优化\"},{\"text\":\"Redis原理\",\"link\":\"/2、数据库/Redis/Redis原理\"},{\"text\":\"Redis高级\",\"link\":\"/2、数据库/Redis/Redis高级\"},{\"text\":\"Redis实战\",\"link\":\"/2、数据库/Redis/Redis实战\"},{\"text\":\"本地缓存\",\"link\":\"/2、数据库/Redis/本地缓存\"}]},{\"text\":\"MongoDB\",\"collapsed\":true,\"items\":[{\"text\":\"MongoDB基础\",\"link\":\"/2、数据库/MongoDB/基础\"},{\"text\":\"MongoDB进阶\",\"link\":\"/2、数据库/MongoDB/整合\"}]},{\"text\":\"ElasticSearch\",\"collapsed\":true,\"items\":[{\"text\":\"ES基础\",\"link\":\"/2、数据库/ElasticSearch/1、ES基础\"},{\"text\":\"ES高级\",\"link\":\"/2、数据库/ElasticSearch/3、ES高级\"}]},{\"text\":\"InfluxDB\",\"link\":\"/2、数据库/influxdb\"},{\"text\":\"Neo4j\",\"link\":\"/2、数据库/Neo4j\"}]},{\"text\":\"高并发 & 秒杀 & 分布式\",\"collapsed\":true,\"items\":[{\"text\":\"分布式理论\",\"link\":\"/三高/分布式\"},{\"text\":\"分布式锁\",\"link\":\"/4、微服务/必备/分布式锁\"},{\"text\":\"秒杀\",\"link\":\"/三高/秒杀\"},{\"text\":\"高可用\",\"link\":\"/三高/高可用\"},{\"text\":\"高并发\",\"link\":\"/三高/高并发\"}]},{\"text\":\"云原生\",\"collapsed\":true,\"items\":[{\"text\":\"Docker\",\"link\":\"/云原生/Docker\"},{\"text\":\"K8S\",\"link\":\"/云原生/K8S\"}]},{\"text\":\"可视化 & 监控\",\"collapsed\":true,\"items\":[{\"text\":\"监控基础\",\"link\":\"/可视化 & 监控/监控基础\"},{\"text\":\"监控进阶\",\"link\":\"/可视化 & 监控/监控进阶\"},{\"text\":\"可视化大屏\",\"link\":\"/可视化 & 监控/可视化大屏\"},{\"text\":\"Zabbix\",\"link\":\"/可视化 & 监控/Zabbix\"}]},{\"text\":\"学前端\",\"collapsed\":true,\"items\":[{\"text\":\"HTML+CSS\",\"collapsed\":true,\"items\":[{\"text\":\"HTML基础\",\"link\":\"/1、学前端/1、HTML+CSS/HTML基础\"},{\"text\":\"CSS基础\",\"link\":\"/1、学前端/1、HTML+CSS/CSS基础\"},{\"text\":\"网页进阶\",\"link\":\"/1、学前端/1、HTML+CSS/网页进阶\"}]},{\"text\":\"JS+TS\",\"collapsed\":true,\"items\":[{\"text\":\"JS基础\",\"link\":\"/1、学前端/2、JS+TS/JS 基础\"},{\"text\":\"JS进阶\",\"link\":\"/1、学前端/2、JS+TS/JS 进阶\"},{\"text\":\"ES6基础\",\"link\":\"/1、学前端/2、JS+TS/ES6 基础\"},{\"text\":\"ES6进阶\",\"link\":\"/1、学前端/2、JS+TS/ES6 进阶\"},{\"text\":\"TS基础\",\"link\":\"/1、学前端/2、JS+TS/TypeScript\"}]},{\"text\":\"NodeJS\",\"collapsed\":true,\"items\":[{\"text\":\"Node基础\",\"link\":\"/1、学前端/4、Node/基础篇\"},{\"text\":\"Node进阶\",\"link\":\"/1、学前端/4、Node/进阶篇\"},{\"text\":\"项目实战\",\"link\":\"/1、学前端/4、Node/项目实战\"}]},{\"text\":\"Vue\",\"collapsed\":true,\"items\":[{\"text\":\"Vue3进阶\",\"link\":\"/1、学前端/3、Vue/Vue3/Vue3进阶\"},{\"text\":\"Vue3高级\",\"link\":\"/1、学前端/3、Vue/Vue3/Vue3高级\"},{\"text\":\"Vue3新语法\",\"link\":\"/1、学前端/3、Vue/Vue3/Vue3新语法\"},{\"text\":\"项目实战\",\"link\":\"/1、学前端/3、Vue/Vue2/Vue2项目\"}]},{\"text\":\"小程序\",\"collapsed\":true,\"items\":[{\"text\":\"小程序基础\",\"link\":\"/1、学前端/5、小程序/微信小程序\"},{\"text\":\"小程序优化\",\"link\":\"/1、学前端/5、小程序/小程序优化\"},{\"text\":\"uniapp\",\"link\":\"/1、学前端/5、小程序/uniapp\"},{\"text\":\"项目实战\",\"link\":\"/1、学前端/5、小程序/小程序项目\"}]}]},{\"text\":\"计算机基础\",\"collapsed\":true,\"items\":[{\"text\":\"数据结构\",\"link\":\"/计算机基础/数据结构/基础篇\"},{\"text\":\"操作系统\",\"link\":\"/计算机基础/计算机基础/操作系统\"},{\"text\":\"设计模式\",\"link\":\"/计算机基础/设计模式/基础篇\"},{\"text\":\"计算机网络\",\"link\":\"/计算机基础/计算机网络/网络基础\"},{\"text\":\"UML\",\"link\":\"/计算机基础/设计模式/UML\"},{\"text\":\"LeetCode\",\"link\":\"/计算机基础/算法/LeetCode\"}]},{\"text\":\"项目实战\",\"collapsed\":true,\"items\":[{\"text\":\"云尚办公\",\"collapsed\":true,\"items\":[{\"text\":\"基础篇\",\"link\":\"/项目实战/云尚办公/基础篇\"}]},{\"text\":\"小兔鲜\",\"collapsed\":true,\"items\":[{\"text\":\"基础篇\",\"link\":\"/项目实战/小兔鲜/基础篇\"},{\"text\":\"进阶篇1\",\"link\":\"/项目实战/小兔鲜/进阶篇1\"},{\"text\":\"进阶篇2\",\"link\":\"/项目实战/小兔鲜/进阶篇2\"}]},{\"text\":\"地图\",\"collapsed\":true,\"items\":[{\"text\":\"基础篇\",\"link\":\"/项目实战/百度地图/基础篇\"},{\"text\":\"进阶篇\",\"link\":\"/项目实战/百度地图/进阶篇\"}]},{\"text\":\"苍穹外卖\",\"collapsed\":true,\"items\":[{\"text\":\"进阶篇\",\"link\":\"/项目实战/苍穹外卖/进阶篇\"}]},{\"text\":\"黑马头条\",\"collapsed\":true,\"items\":[{\"text\":\"基础篇\",\"link\":\"/项目实战/黑马头条/基础篇\"},{\"text\":\"进阶篇\",\"link\":\"/项目实战/黑马头条/进阶篇\"},{\"text\":\"进阶篇2\",\"link\":\"/项目实战/黑马头条/进阶篇2\"},{\"text\":\"高级篇\",\"link\":\"/项目实战/黑马头条/高级篇\"}]},{\"text\":\"支付\",\"link\":\"/项目实战/支付\"},{\"text\":\"项目推荐\",\"link\":\"/项目实战/项目推荐\"}]},{\"text\":\"团队成员\",\"link\":\"/team\"}],\"siteTitle\":\"任硕的文档\",\"logo\":\"/Vue.png\",\"nav\":[{\"text\":\"Java学前端\",\"items\":[{\"items\":[{\"text\":\"HTML+JS\",\"link\":\"/Java学前端/HTML+JS\"},{\"text\":\"CSS\",\"link\":\"/Java学前端/CSS\"},{\"text\":\"Vue2+组件\",\"link\":\"/Java学前端/Vue2+组件\"},{\"text\":\"Vue3+组件\",\"link\":\"/Java学前端/Vue3+组件\"},{\"text\":\"React\",\"link\":\"/Java学前端/React\"}]}],\"activeMatch\":\"/Java/\"},{\"text\":\"软件测试\",\"items\":[{\"items\":[{\"text\":\"测试基础\",\"link\":\"/软件测试/测试基础\"},{\"text\":\"压力测试\",\"link\":\"/软件测试/压力测试\"}]}]},{\"text\":\"多线程\",\"items\":[{\"items\":[{\"text\":\"基础篇\",\"link\":\"/并发 & 多线程/基础篇\"},{\"text\":\"进阶篇\",\"link\":\"/并发 & 多线程/并发完善\"}]}]},{\"text\":\"开发工具\",\"items\":[{\"items\":[{\"text\":\"Chrome\",\"link\":\"/IDEA/Chrome\"},{\"text\":\"IDEA基础\",\"link\":\"/IDEA/IDEA基础\"},{\"text\":\"IDEA插件\",\"link\":\"/IDEA/IDEA插件\"},{\"text\":\"VS Code\",\"link\":\"/IDEA/VS Code\"}]}]},{\"text\":\"消息中间件\",\"items\":[{\"items\":[{\"text\":\"RabbitMQ\",\"link\":\"/消息中间件/RabbitMQ\"},{\"text\":\"RocketMQ\",\"link\":\"/消息中间件/RocketMQ\"},{\"text\":\"Kafka\",\"link\":\"/消息中间件/Kafka\"},{\"text\":\"Canal\",\"link\":\"/消息中间件/Canal\"}]}]}],\"socialLinks\":[{\"icon\":\"github\",\"link\":\"https://github.com/renshuo123/renshuo123.github.io\"},{\"icon\":\"twitter\",\"link\":\"#\"},{\"icon\":{\"svg\":\"<svg t=\\\"1676028692954\\\" class=\\\"icon\\\" ...</path></svg>\"},\"link\":\"https://github.com/\"}]},\"locales\":{},\"scrollOffset\":90,\"cleanUrls\":false}");</script>
    
  </body>
</html>